From 405b9555645d04e91bc2bcd2ade384b991b18e54 Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 18 Nov 2022 09:33:20 -0500 Subject: [PATCH 001/231] tweaks to besapi --- README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 171aabd..6120da4 100644 --- a/README.md +++ b/README.md @@ -91,27 +91,27 @@ OR >>> import bescli >>> bescli.main() -BES> login +BigFix> login User [mah60]: mah60 Root Server (ex. https://server.institution.edu:52311): https://my.company.org:52311 Password: Login Successful! -BES> get help +BigFix> get help ... -BES> get sites +BigFix> get sites ... -BES> get sites.OperatorSite.Name +BigFix> get sites.OperatorSite.Name mah60 -BES> get help/fixlets +BigFix> get help/fixlets GET: /api/fixlets/{site} POST: /api/fixlets/{site} -BES> get fixlets/operator/mah60 +BigFix> get fixlets/operator/mah60 ... ``` -# REST API Help +# BigFix REST API Documentation - https://developer.bigfix.com/rest-api/ - http://bigfix.me/restapi @@ -124,6 +124,13 @@ BES> get fixlets/operator/mah60 - requests - cmd2 +# Examples using BESAPI + +- https://github.com/jgstew/generate_bes_from_template/blob/master/examples/generate_uninstallers.py +- https://github.com/jgstew/jgstew-recipes/blob/main/SharedProcessors/BESImport.py +- https://github.com/jgstew/jgstew-recipes/blob/main/SharedProcessors/BigFixActioner.py +- https://github.com/jgstew/jgstew-recipes/blob/main/SharedProcessors/BigFixSessionRelevance.py + # Pyinstaller - `pyinstaller --clean --collect-all besapi --onefile .\src\bescli\bescli.py` From 5045c89f4fe1f79d0e94b7b7fa49aed5eff37622 Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 18 Nov 2022 10:05:39 -0500 Subject: [PATCH 002/231] add get_bes_conn_using_config_file to besapi, update precommit, add test --- .pre-commit-config.yaml | 10 +++------ src/besapi/besapi.py | 46 +++++++++++++++++++++++++++++++++++++++-- tests/tests.py | 2 ++ 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 272f339..2a63908 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ # https://github.com/pre-commit/pre-commit-hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.3.0 hooks: - id: check-yaml - id: check-json @@ -27,7 +27,7 @@ repos: # - id: no-commit-to-branch # args: [--branch, main] - repo: https://github.com/adrienverge/yamllint.git - rev: v1.26.3 + rev: v1.28.0 hooks: - id: yamllint args: [-c=.yamllint.yaml] @@ -35,11 +35,7 @@ repos: rev: v5.10.1 hooks: - id: isort - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 - hooks: - - id: flake8 - repo: https://github.com/psf/black - rev: 22.1.0 + rev: 22.10.0 hooks: - id: black diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 15511cc..7585fc4 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -9,6 +9,7 @@ Library for communicating with the BES (BigFix) REST API. """ +import configparser import datetime import json import logging @@ -17,8 +18,6 @@ import site import string -# import urllib3.poolmanager - try: from urllib import parse except ImportError: @@ -113,6 +112,7 @@ def parse_bes_modtime(string_datetime): return datetime.datetime.strptime(string_datetime, "%a, %d %b %Y %H:%M:%S %z") +# import urllib3.poolmanager # # https://docs.python-requests.org/en/latest/user/advanced/#transport-adapters # class HTTPAdapterBiggerBlocksize(requests.adapters.HTTPAdapter): # """custom HTTPAdapter for requests to override blocksize @@ -150,6 +150,48 @@ def parse_bes_modtime(string_datetime): # ) +def get_bes_conn_using_config_file(conf_file=None): + """ + read connection values from config file + return besapi connection + """ + config_paths = [ + "/etc/besapi.conf", + os.path.expanduser("~/besapi.conf"), + os.path.expanduser("~/.besapi.conf"), + "besapi.conf", + ] + # if conf_file specified, then only use that: + if conf_file: + config_paths = [conf_file] + + configparser_instance = configparser.ConfigParser() + + found_config_files = configparser_instance.read(config_paths) + + if found_config_files and configparser_instance: + print("Attempting BESAPI Connection using config file:", found_config_files) + try: + BES_ROOT_SERVER = configparser_instance.get("besapi", "BES_ROOT_SERVER") + except BaseException: + BES_ROOT_SERVER = None + + try: + BES_USER_NAME = configparser_instance.get("besapi", "BES_USER_NAME") + except BaseException: + BES_USER_NAME = None + + try: + BES_PASSWORD = configparser_instance.get("besapi", "BES_PASSWORD") + except BaseException: + BES_PASSWORD = None + + if BES_ROOT_SERVER and BES_USER_NAME and BES_PASSWORD: + return BESConnection(BES_USER_NAME, BES_PASSWORD, BES_ROOT_SERVER) + + return None + + class BESConnection: """BigFix RESTAPI connection abstraction class""" diff --git a/tests/tests.py b/tests/tests.py index b4d3f21..a5e3def 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -132,3 +132,5 @@ class RequestResult(object): 'CMD /C python -m besapi ls clear ls conf "query number of bes computers" version error_count exit', check=True, ) + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + print("login succeeded:", bes_conn.login()) From 77c319d1c4b7b1e3c9c0d26ec1089fb10836dc63 Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 18 Nov 2022 10:14:09 -0500 Subject: [PATCH 003/231] update actions, tag a new release. --- .github/workflows/tag_and_release.yaml | 6 +++--- .github/workflows/test_build.yaml | 6 +++--- src/besapi/__init__.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tag_and_release.yaml b/.github/workflows/tag_and_release.yaml index fb55cb6..ba7e1b7 100644 --- a/.github/workflows/tag_and_release.yaml +++ b/.github/workflows/tag_and_release.yaml @@ -15,10 +15,10 @@ jobs: name: Tag and Release runs-on: ubuntu-latest steps: - - name: "Checkout source code" - uses: "actions/checkout@v1" + - name: Checkout source code + uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.8 - name: Read VERSION file diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index 2d973a9..a9af712 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -30,9 +30,9 @@ jobs: # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json python-version: ["3.6", "3"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} - name: Install build tools @@ -61,7 +61,7 @@ jobs: run: python -m bescli ls logout clear error_count version exit - name: Run Tests - Pip run: python tests/tests.py --test_pip - - name: Test pyinstaller + - name: Test pyinstaller build run: pyinstaller --clean --collect-all besapi --onefile ./src/bescli/bescli.py - name: Test bescli binary run: ./dist/bescli ls logout clear error_count version exit diff --git a/src/besapi/__init__.py b/src/besapi/__init__.py index 28f4ec5..a137621 100644 --- a/src/besapi/__init__.py +++ b/src/besapi/__init__.py @@ -6,4 +6,4 @@ from . import besapi -__version__ = "3.0.2" +__version__ = "3.1.1" From 2f144ee1e1d731db48b3c167dca4d148a71e5a46 Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 18 Nov 2022 11:32:32 -0500 Subject: [PATCH 004/231] create export_all_sites example script --- example/export_all_sites.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 example/export_all_sites.py diff --git a/example/export_all_sites.py b/example/export_all_sites.py new file mode 100644 index 0000000..84523cc --- /dev/null +++ b/example/export_all_sites.py @@ -0,0 +1,20 @@ +import os + +import besapi + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + os.mkdir("export") + + os.chdir("export") + + bes_conn.export_all_sites() + + +if __name__ == "__main__": + main() From 51b14da58d120f5888d09a15f5d189cb8257c68c Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 18 Nov 2022 11:41:25 -0500 Subject: [PATCH 005/231] add comments --- example/export_all_sites.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/example/export_all_sites.py b/example/export_all_sites.py index 84523cc..25ff854 100644 --- a/example/export_all_sites.py +++ b/example/export_all_sites.py @@ -1,3 +1,9 @@ +""" +This will export all bigfix sites to a folder called `export` + +This is equivalent of running `python -m besapi export_all_sites` +""" + import os import besapi From 94cfb2f2fa8d861b25f006be8c1db052c9a4d10a Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 18 Nov 2022 11:42:06 -0500 Subject: [PATCH 006/231] rename folder --- {example => examples}/export_all_sites.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {example => examples}/export_all_sites.py (100%) diff --git a/example/export_all_sites.py b/examples/export_all_sites.py similarity index 100% rename from example/export_all_sites.py rename to examples/export_all_sites.py From 6363638489ccd562036f04adcc0cbd51392e7fb6 Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 18 Nov 2022 11:58:45 -0500 Subject: [PATCH 007/231] working example stop_open_completed_actions --- examples/stop_open_completed_actions.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 examples/stop_open_completed_actions.py diff --git a/examples/stop_open_completed_actions.py b/examples/stop_open_completed_actions.py new file mode 100644 index 0000000..4dd740f --- /dev/null +++ b/examples/stop_open_completed_actions.py @@ -0,0 +1,24 @@ +import besapi + +SESSION_RELEVANCE = """ids of bes actions whose( (targeted by list flag of it OR targeted by id flag of it) AND not reapply flag of it AND not group member flag of it AND "Open"=state of it AND (now - time issued of it) >= 8 * day )""" + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + session_result = bes_conn.session_relevance_array(SESSION_RELEVANCE) + + # print(session_result) + + # https://developer.bigfix.com/rest-api/api/action.html + for action_id in session_result: + print("Stopping Action:", action_id) + action_stop_result = bes_conn.post("action/" + action_id + "/stop", "") + print(action_stop_result) + + +if __name__ == "__main__": + main() From c38ffc6be436fa3b72b6c4ce0f547f1fdc74431f Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 18 Nov 2022 12:07:07 -0500 Subject: [PATCH 008/231] add another session relevane example --- examples/stop_open_completed_actions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/stop_open_completed_actions.py b/examples/stop_open_completed_actions.py index 4dd740f..13f8dd7 100644 --- a/examples/stop_open_completed_actions.py +++ b/examples/stop_open_completed_actions.py @@ -1,5 +1,8 @@ import besapi +# another session relevance option: +# ids of bes actions whose( ("Expired" = state of it OR "Stopped" = state of it) AND (now - time issued of it > 180 * day) ) + SESSION_RELEVANCE = """ids of bes actions whose( (targeted by list flag of it OR targeted by id flag of it) AND not reapply flag of it AND not group member flag of it AND "Open"=state of it AND (now - time issued of it) >= 8 * day )""" From 540773810cb31d02c070477aa193fef752dc3d6c Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 18 Nov 2022 12:20:18 -0500 Subject: [PATCH 009/231] example to send message to all computers --- examples/send_message_all_computers.py | 109 +++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 examples/send_message_all_computers.py diff --git a/examples/send_message_all_computers.py b/examples/send_message_all_computers.py new file mode 100644 index 0000000..56e90e6 --- /dev/null +++ b/examples/send_message_all_computers.py @@ -0,0 +1,109 @@ +import besapi + +MESSAGE_XML = r""" + + + Test message from besapi + = ("3.1.0" as version)) of key "HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall" of x32 registry) else if (mac of operating system) then (exists application "BigFixSSA.app" whose (version of it >= "3.1.0")) else false) AND (exists line whose (it = "disableMessagesTab: false") of file (if (windows of operating system) then (pathname of parent folder of parent folder of client) & "\BigFix Self Service Application\resources\ssa.config" else "/Library/Application Support/BigFix/BigFixSSA/ssa.config"))]]> + //Nothing to do + + + Test message from besapi + true + + Test message from besapi

]]>
+ false + false + false + ForceToRun + Interval + P3D + false +
+ false + false + false + false + false + false + NoRequirement + AllUsers + false + false + false + true + 3 + false + false + false + false + + false +
+ + false + false + + false + false + false + false + false + false + + false + + false + + false + false + false + false + false + false + false + false + false + false + false + false + false + false + + false + false + false + false + false + + false + false + false + false + + true + + true + + + action-ui-metadata + {"type":"notification","sender":"broadcast","expirationDays":3} + +
+
+""" + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + result = bes_conn.post("actions", MESSAGE_XML) + + print(result) + + +if __name__ == "__main__": + main() From 599aedb5926ae6808fa336289deb851b62a0ba28 Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 18 Nov 2022 12:30:10 -0500 Subject: [PATCH 010/231] add examples --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6120da4..9050a08 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,7 @@ BigFix> get fixlets/operator/mah60 # Examples using BESAPI +- https://github.com/jgstew/besapi/tree/master/examples - https://github.com/jgstew/generate_bes_from_template/blob/master/examples/generate_uninstallers.py - https://github.com/jgstew/jgstew-recipes/blob/main/SharedProcessors/BESImport.py - https://github.com/jgstew/jgstew-recipes/blob/main/SharedProcessors/BigFixActioner.py From d643f6eb2929c3ffa4e2131baa6d1e61c95ad69d Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 1 Dec 2022 17:34:19 -0500 Subject: [PATCH 011/231] add examples --- examples/parameters_sourced_fixlet_action.py | 37 ++++++++++++++++++++ examples/send_message_all_computers.py | 4 +-- 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 examples/parameters_sourced_fixlet_action.py diff --git a/examples/parameters_sourced_fixlet_action.py b/examples/parameters_sourced_fixlet_action.py new file mode 100644 index 0000000..77295d8 --- /dev/null +++ b/examples/parameters_sourced_fixlet_action.py @@ -0,0 +1,37 @@ +import besapi + +# reference: https://software.bigfix.com/download/bes/100/util/BES10.0.7.52.xsd +# https://forum.bigfix.com/t/api-sourcedfixletaction-including-end-time/37117/2 +# https://forum.bigfix.com/t/secret-parameter-actions/38847/13 + +CONTENT_XML = r""" + + + + BES Support + 15 + Action1 + + + true + + test_value + Test parameters - SourcedFixletAction - BES Clients Have Incorrect Clock Time + + +""" + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + result = bes_conn.post("actions", CONTENT_XML) + + print(result) + + +if __name__ == "__main__": + main() diff --git a/examples/send_message_all_computers.py b/examples/send_message_all_computers.py index 56e90e6..7ed2d8e 100644 --- a/examples/send_message_all_computers.py +++ b/examples/send_message_all_computers.py @@ -1,6 +1,6 @@ import besapi -MESSAGE_XML = r""" +CONTENT_XML = r""" Test message from besapi @@ -100,7 +100,7 @@ def main(): bes_conn = besapi.besapi.get_bes_conn_using_config_file() bes_conn.login() - result = bes_conn.post("actions", MESSAGE_XML) + result = bes_conn.post("actions", CONTENT_XML) print(result) From 78b0aa7047679382557eaed15594d88c634677d7 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 1 Dec 2022 17:41:23 -0500 Subject: [PATCH 012/231] fix indentation. --- examples/parameters_sourced_fixlet_action.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/parameters_sourced_fixlet_action.py b/examples/parameters_sourced_fixlet_action.py index 77295d8..cdb0b0e 100644 --- a/examples/parameters_sourced_fixlet_action.py +++ b/examples/parameters_sourced_fixlet_action.py @@ -6,18 +6,18 @@ CONTENT_XML = r""" - - - BES Support - 15 - Action1 - - + + + BES Support + 15 + Action1 + + true - test_value - Test parameters - SourcedFixletAction - BES Clients Have Incorrect Clock Time - + test_value + Test parameters - SourcedFixletAction - BES Clients Have Incorrect Clock Time + """ From 9ceae5e61e13b475982b495a0eb14c143487c502 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 1 Dec 2022 18:04:34 -0500 Subject: [PATCH 013/231] update examples --- ...parameters_secure_sourced_fixlet_action.py | 50 +++++++++++++++++++ examples/parameters_sourced_fixlet_action.py | 2 +- 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 examples/parameters_secure_sourced_fixlet_action.py diff --git a/examples/parameters_secure_sourced_fixlet_action.py b/examples/parameters_secure_sourced_fixlet_action.py new file mode 100644 index 0000000..b46ce5b --- /dev/null +++ b/examples/parameters_secure_sourced_fixlet_action.py @@ -0,0 +1,50 @@ +import besapi + +# reference: https://software.bigfix.com/download/bes/100/util/BES10.0.7.52.xsd +# https://forum.bigfix.com/t/api-sourcedfixletaction-including-end-time/37117/2 +# https://forum.bigfix.com/t/secret-parameter-actions/38847/13 + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + # SessionRelevance for root server id: + session_relevance = """ + maxima of ids of bes computers + whose(root server flag of it AND now - last report time of it < 1 * day) + """ + + root_server_id = int(bes_conn.session_relevance_string(session_relevance)) + + CONTENT_XML = ( + r""" + + + + BES Support + 15 + Action1 + + + """ + + str(root_server_id) + + r""" + + test_value + test_secure_value + Test parameters - secure - SourcedFixletAction - BES Clients Have Incorrect Clock Time + + +""" + ) + + result = bes_conn.post("actions", CONTENT_XML) + + print(result) + + +if __name__ == "__main__": + main() diff --git a/examples/parameters_sourced_fixlet_action.py b/examples/parameters_sourced_fixlet_action.py index cdb0b0e..c82d7b2 100644 --- a/examples/parameters_sourced_fixlet_action.py +++ b/examples/parameters_sourced_fixlet_action.py @@ -13,7 +13,7 @@ Action1 - true + BIGFIX test_value Test parameters - SourcedFixletAction - BES Clients Have Incorrect Clock Time From e0e5058fcbe337632f76777ec1a020476e621dc9 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 1 Dec 2022 18:09:20 -0500 Subject: [PATCH 014/231] tabs to spaces --- examples/send_message_all_computers.py | 172 ++++++++++++------------- 1 file changed, 86 insertions(+), 86 deletions(-) diff --git a/examples/send_message_all_computers.py b/examples/send_message_all_computers.py index 7ed2d8e..54489af 100644 --- a/examples/send_message_all_computers.py +++ b/examples/send_message_all_computers.py @@ -2,94 +2,94 @@ CONTENT_XML = r""" - - Test message from besapi - = ("3.1.0" as version)) of key "HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall" of x32 registry) else if (mac of operating system) then (exists application "BigFixSSA.app" whose (version of it >= "3.1.0")) else false) AND (exists line whose (it = "disableMessagesTab: false") of file (if (windows of operating system) then (pathname of parent folder of parent folder of client) & "\BigFix Self Service Application\resources\ssa.config" else "/Library/Application Support/BigFix/BigFixSSA/ssa.config"))]]> - //Nothing to do - - - Test message from besapi - true - - Test message from besapi

]]>
- false - false - false - ForceToRun - Interval - P3D - false -
- false - false - false - false - false - false - NoRequirement - AllUsers - false - false - false - true - 3 - false - false - false - false - - false -
- - false - false - - false - false - false - false - false - false - - false - - false - - false - false - false - false - false - false - false - false - false - false - false - false - false - false - - false - false - false - false - false - - false - false - false - false - - true - + + Test message from besapi + = ("3.1.0" as version)) of key "HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall" of x32 registry) else if (mac of operating system) then (exists application "BigFixSSA.app" whose (version of it >= "3.1.0")) else false) AND (exists line whose (it = "disableMessagesTab: false") of file (if (windows of operating system) then (pathname of parent folder of parent folder of client) & "\BigFix Self Service Application\resources\ssa.config" else "/Library/Application Support/BigFix/BigFixSSA/ssa.config"))]]> + //Nothing to do + + + Test message from besapi + true + + Test message from besapi

]]>
+ false + false + false + ForceToRun + Interval + P3D + false +
+ false + false + false + false + false + false + NoRequirement + AllUsers + false + false + false + true + 3 + false + false + false + false + + false +
+ + false + false + + false + false + false + false + false + false + + false + + false + + false + false + false + false + false + false + false + false + false + false + false + false + false + false + + false + false + false + false + false + + false + false + false + false + + true + true - - action-ui-metadata - {"type":"notification","sender":"broadcast","expirationDays":3} - -
+ + action-ui-metadata + {"type":"notification","sender":"broadcast","expirationDays":3} + +
""" From f99b87f6fb99ec5ecf24c0e434764ff1f53a628d Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 2 Dec 2022 12:04:45 -0500 Subject: [PATCH 015/231] add comment --- examples/parameters_secure_sourced_fixlet_action.py | 5 +++++ examples/parameters_sourced_fixlet_action.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/examples/parameters_secure_sourced_fixlet_action.py b/examples/parameters_secure_sourced_fixlet_action.py index b46ce5b..afeec9d 100644 --- a/examples/parameters_secure_sourced_fixlet_action.py +++ b/examples/parameters_secure_sourced_fixlet_action.py @@ -1,3 +1,8 @@ +""" +Example sourced fixlet action with parameters + +requires `besapi`, install with command `pip install besapi` +""" import besapi # reference: https://software.bigfix.com/download/bes/100/util/BES10.0.7.52.xsd diff --git a/examples/parameters_sourced_fixlet_action.py b/examples/parameters_sourced_fixlet_action.py index c82d7b2..70c4a04 100644 --- a/examples/parameters_sourced_fixlet_action.py +++ b/examples/parameters_sourced_fixlet_action.py @@ -1,3 +1,8 @@ +""" +Example sourced fixlet action with parameters + +requires `besapi`, install with command `pip install besapi` +""" import besapi # reference: https://software.bigfix.com/download/bes/100/util/BES10.0.7.52.xsd From ed62049156e12758e3902cf9611c52469d642613 Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 2 Dec 2022 12:50:07 -0500 Subject: [PATCH 016/231] example session relevance in python reading and writing to files --- examples/parameters_sourced_fixlet_action.py | 2 +- examples/session_relevance_from_file.py | 27 ++++++++++++++++++++ examples/session_relevance_query_input.txt | 1 + 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 examples/session_relevance_from_file.py create mode 100644 examples/session_relevance_query_input.txt diff --git a/examples/parameters_sourced_fixlet_action.py b/examples/parameters_sourced_fixlet_action.py index 70c4a04..602bdf4 100644 --- a/examples/parameters_sourced_fixlet_action.py +++ b/examples/parameters_sourced_fixlet_action.py @@ -20,7 +20,7 @@ BIGFIX - test_value + test_value_demo Test parameters - SourcedFixletAction - BES Clients Have Incorrect Clock Time
diff --git a/examples/session_relevance_from_file.py b/examples/session_relevance_from_file.py new file mode 100644 index 0000000..6063ffd --- /dev/null +++ b/examples/session_relevance_from_file.py @@ -0,0 +1,27 @@ +""" +Example session relevance results from a file + +requires `besapi`, install with command `pip install besapi` +""" +import besapi + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + with open("examples/session_relevance_query_input.txt") as file: + session_relevance = file.read() + + result = bes_conn.session_relevance_string(session_relevance) + + print(result) + + with open("examples/session_relevance_query_output.txt", "w") as file_out: + file_out.write(result) + + +if __name__ == "__main__": + main() diff --git a/examples/session_relevance_query_input.txt b/examples/session_relevance_query_input.txt new file mode 100644 index 0000000..45ffa5a --- /dev/null +++ b/examples/session_relevance_query_input.txt @@ -0,0 +1 @@ +(it as string) of (name of it, id of it) of members of bes computer groups whose (name of it = "Windows non-BigFix") From a24765bcae4a7c35149a27c4c6a47db59e13a47a Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 2 Dec 2022 13:01:02 -0500 Subject: [PATCH 017/231] session relevance example json --- examples/session_relevance_json.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 examples/session_relevance_json.py diff --git a/examples/session_relevance_json.py b/examples/session_relevance_json.py new file mode 100644 index 0000000..a80a535 --- /dev/null +++ b/examples/session_relevance_json.py @@ -0,0 +1,27 @@ +""" +Example session relevance results in json format + +This is much more fragile because it uses GET instead of POST + +requires `besapi`, install with command `pip install besapi` +""" +import besapi + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + session_relevance = """names of members of bes computer groups whose (name of it = "Windows non-BigFix")""" + + result = bes_conn.get( + bes_conn.url(f"query?output=json&relevance={session_relevance}") + ) + + print(result) + + +if __name__ == "__main__": + main() From 6b4a476f60a6ae43e021de718db63d062a968264 Mon Sep 17 00:00:00 2001 From: jgstew Date: Tue, 6 Dec 2022 12:07:11 -0500 Subject: [PATCH 018/231] non working example --- examples/wake_on_lan.py | 48 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 examples/wake_on_lan.py diff --git a/examples/wake_on_lan.py b/examples/wake_on_lan.py new file mode 100644 index 0000000..8bb9d5d --- /dev/null +++ b/examples/wake_on_lan.py @@ -0,0 +1,48 @@ +""" +Send Wake On Lan (WoL) request to given computer IDs + +requires `besapi`, install with command `pip install besapi` + +Related: + +- http://localhost:__WebReportsPort__/json/wakeonlan?cid=_ComputerID_&cid=_NComputerID_ +- https://localhost:52311/rd-proxy?RequestUrl=cgi-bin/bfenterprise/BESGatherMirrorNew.exe/-triggergatherdb?forwardtrigger +- https://localhost:52311/rd-proxy?RequestUrl=../../cgi-bin/bfenterprise/ClientRegister.exe?RequestType=GetComputerID +- https://localhost:52311/rd-proxy?RequestUrl=cgi-bin/bfenterprise/BESGatherMirror.exe/-besgather&body=SiteContents&url=http://_MASTHEAD_FQDN_:52311/cgi-bin/bfgather.exe/actionsite +- https://localhost:52311/rd-proxy?RequestUrl=cgi-bin/bfenterprise/BESMirrorRequest.exe/-textreport +- Gather Download Request: https://localhost:52311/rd-proxy?RequestUrl=bfmirror/downloads/_ACTION_ID_/_DOWNLOAD_ID_ +""" +import sys + +import besapi + + +def main(): + """Execution starts here""" + print("main()") + print("WORK IN PROGRESS! Does not work currently! Exiting!") + sys.exit(-1) + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + # SessionRelevance for root server id: + session_relevance = """ + maxima of ids of bes computers + whose(root server flag of it AND now - last report time of it < 1 * day) + """ + + root_server_id = int(bes_conn.session_relevance_string(session_relevance)) + + print( + f"Work in progress POST {bes_conn.rootserver}/data/wake-on-lan {root_server_id}" + ) + + print( + bes_conn.session.post( + f"{bes_conn.rootserver}/data/wake-on-lan", data=f"cid={root_server_id}" + ) + ) + + +if __name__ == "__main__": + main() From 94de7d909f3eb1c77564d60551adf9061073745e Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 7 Dec 2022 10:13:21 -0500 Subject: [PATCH 019/231] working WoL example --- examples/wake_on_lan.py | 50 +++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/examples/wake_on_lan.py b/examples/wake_on_lan.py index 8bb9d5d..8e20187 100644 --- a/examples/wake_on_lan.py +++ b/examples/wake_on_lan.py @@ -6,6 +6,7 @@ Related: - http://localhost:__WebReportsPort__/json/wakeonlan?cid=_ComputerID_&cid=_NComputerID_ +- POST(binary) http://localhost:52311/data/wake-on-lan - https://localhost:52311/rd-proxy?RequestUrl=cgi-bin/bfenterprise/BESGatherMirrorNew.exe/-triggergatherdb?forwardtrigger - https://localhost:52311/rd-proxy?RequestUrl=../../cgi-bin/bfenterprise/ClientRegister.exe?RequestType=GetComputerID - https://localhost:52311/rd-proxy?RequestUrl=cgi-bin/bfenterprise/BESGatherMirror.exe/-besgather&body=SiteContents&url=http://_MASTHEAD_FQDN_:52311/cgi-bin/bfgather.exe/actionsite @@ -20,8 +21,7 @@ def main(): """Execution starts here""" print("main()") - print("WORK IN PROGRESS! Does not work currently! Exiting!") - sys.exit(-1) + bes_conn = besapi.besapi.get_bes_conn_using_config_file() bes_conn.login() @@ -33,16 +33,48 @@ def main(): root_server_id = int(bes_conn.session_relevance_string(session_relevance)) - print( - f"Work in progress POST {bes_conn.rootserver}/data/wake-on-lan {root_server_id}" - ) + print(root_server_id) - print( - bes_conn.session.post( - f"{bes_conn.rootserver}/data/wake-on-lan", data=f"cid={root_server_id}" - ) + soap_xml = ( + """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +""" ) + print(bes_conn.session.post(f"{bes_conn.rootserver}/WakeOnLan", data=soap_xml)) + + print("Finished, Response 200 means succces.") + if __name__ == "__main__": main() From 909a03aef872767d4adba9bbc38046414576258d Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 7 Dec 2022 10:19:13 -0500 Subject: [PATCH 020/231] now working for multiple computer ids --- examples/wake_on_lan.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/wake_on_lan.py b/examples/wake_on_lan.py index 8e20187..63ec0c8 100644 --- a/examples/wake_on_lan.py +++ b/examples/wake_on_lan.py @@ -31,9 +31,16 @@ def main(): whose(root server flag of it AND now - last report time of it < 1 * day) """ - root_server_id = int(bes_conn.session_relevance_string(session_relevance)) + computer_id_array = bes_conn.session_relevance_array(session_relevance) - print(root_server_id) + print(computer_id_array) + + computer_id_xml_string = "" + + for item in computer_id_array: + computer_id_xml_string += '' + + print(computer_id_xml_string) soap_xml = ( """ @@ -42,9 +49,9 @@ def main(): - + """ + + computer_id_xml_string + + """ From 0e0c7925f510cf3ec37f5143c91035e3c64274da Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 7 Dec 2022 10:28:32 -0500 Subject: [PATCH 021/231] remove debugging print statements. add comments --- examples/wake_on_lan.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/wake_on_lan.py b/examples/wake_on_lan.py index 63ec0c8..0d54f2c 100644 --- a/examples/wake_on_lan.py +++ b/examples/wake_on_lan.py @@ -13,8 +13,6 @@ - https://localhost:52311/rd-proxy?RequestUrl=cgi-bin/bfenterprise/BESMirrorRequest.exe/-textreport - Gather Download Request: https://localhost:52311/rd-proxy?RequestUrl=bfmirror/downloads/_ACTION_ID_/_DOWNLOAD_ID_ """ -import sys - import besapi @@ -25,7 +23,9 @@ def main(): bes_conn = besapi.besapi.get_bes_conn_using_config_file() bes_conn.login() - # SessionRelevance for root server id: + # SessionRelevance for computer ids you wish to wake: + # this currently returns the root server itself, which should have no real effect. + # change this to a singular or plural result of computer ids you wish to wake. session_relevance = """ maxima of ids of bes computers whose(root server flag of it AND now - last report time of it < 1 * day) @@ -33,14 +33,14 @@ def main(): computer_id_array = bes_conn.session_relevance_array(session_relevance) - print(computer_id_array) + # print(computer_id_array) computer_id_xml_string = "" for item in computer_id_array: computer_id_xml_string += '' - print(computer_id_xml_string) + # print(computer_id_xml_string) soap_xml = ( """ From ef2a4b427681b550b9c8553f27f165866f4aa95c Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 7 Dec 2022 10:44:01 -0500 Subject: [PATCH 022/231] session relevance tweak --- examples/wake_on_lan.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/wake_on_lan.py b/examples/wake_on_lan.py index 0d54f2c..d34ea72 100644 --- a/examples/wake_on_lan.py +++ b/examples/wake_on_lan.py @@ -27,8 +27,8 @@ def main(): # this currently returns the root server itself, which should have no real effect. # change this to a singular or plural result of computer ids you wish to wake. session_relevance = """ - maxima of ids of bes computers - whose(root server flag of it AND now - last report time of it < 1 * day) + ids of bes computers + whose(root server flag of it AND now - last report time of it < 10 * day) """ computer_id_array = bes_conn.session_relevance_array(session_relevance) From 55cba76cc8fe13fdcab1d39ab718aa5d3d36959e Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 7 Dec 2022 10:56:51 -0500 Subject: [PATCH 023/231] rename variable --- examples/wake_on_lan.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/wake_on_lan.py b/examples/wake_on_lan.py index d34ea72..215df13 100644 --- a/examples/wake_on_lan.py +++ b/examples/wake_on_lan.py @@ -35,12 +35,12 @@ def main(): # print(computer_id_array) - computer_id_xml_string = "" + computer_ids_xml_string = "" for item in computer_id_array: - computer_id_xml_string += '' + computer_ids_xml_string += '' - # print(computer_id_xml_string) + # print(computer_ids_xml_string) soap_xml = ( """ @@ -50,7 +50,7 @@ def main(): """ - + computer_id_xml_string + + computer_ids_xml_string + """ From c952a64d724e945ce3b3fea3b4904cfca35b6bdb Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 7 Dec 2022 10:58:54 -0500 Subject: [PATCH 024/231] update test build --- .github/workflows/test_build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index a9af712..88472a6 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -28,7 +28,7 @@ jobs: matrix: os: [ubuntu-latest, macos-latest] # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json - python-version: ["3.6", "3"] + python-version: ["3.7", "3"] steps: - uses: actions/checkout@v3 - name: Set up Python From 3a42faa225e47fb950e5a44b97f945aefceee210 Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 7 Dec 2022 11:19:41 -0500 Subject: [PATCH 025/231] get json results from session relevance using POST instead of GET. --- examples/session_relevance_json.py | 9 +++++---- examples/session_relevance_query_input.txt | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/session_relevance_json.py b/examples/session_relevance_json.py index a80a535..14da804 100644 --- a/examples/session_relevance_json.py +++ b/examples/session_relevance_json.py @@ -14,11 +14,12 @@ def main(): bes_conn = besapi.besapi.get_bes_conn_using_config_file() bes_conn.login() - session_relevance = """names of members of bes computer groups whose (name of it = "Windows non-BigFix")""" + with open("examples/session_relevance_query_input.txt") as file: + session_relevance = file.read() - result = bes_conn.get( - bes_conn.url(f"query?output=json&relevance={session_relevance}") - ) + data = {"output": "json", "relevance": session_relevance} + + result = bes_conn.post(bes_conn.url("query"), data) print(result) diff --git a/examples/session_relevance_query_input.txt b/examples/session_relevance_query_input.txt index 45ffa5a..c28d95b 100644 --- a/examples/session_relevance_query_input.txt +++ b/examples/session_relevance_query_input.txt @@ -1 +1 @@ -(it as string) of (name of it, id of it) of members of bes computer groups whose (name of it = "Windows non-BigFix") +(it as string) of (name of it, id of it) of members of items 1 of (it, bes computer groups) whose(item 0 of it = id of item 1 of it) of minimum of ids of bes computer groups whose (exists members of it) From ff145f09ee31c4d11d7ad7ea955e51e46b24e168 Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 7 Dec 2022 18:02:43 -0500 Subject: [PATCH 026/231] tweak --- examples/wake_on_lan.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/wake_on_lan.py b/examples/wake_on_lan.py index 215df13..61f2498 100644 --- a/examples/wake_on_lan.py +++ b/examples/wake_on_lan.py @@ -78,9 +78,12 @@ def main(): """ ) - print(bes_conn.session.post(f"{bes_conn.rootserver}/WakeOnLan", data=soap_xml)) + result = bes_conn.session.post(f"{bes_conn.rootserver}/WakeOnLan", data=soap_xml) - print("Finished, Response 200 means succces.") + print(result) + print(result.text) + + print("Finished, Response 200 should mean succces.") if __name__ == "__main__": From 0956e972f1ac30d82ef123796d27d489f878bf3e Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 16 Feb 2023 15:56:17 -0500 Subject: [PATCH 027/231] add get_upload function to besapi --- examples/get_upload.py | 36 ++++++++++++++++++++++++++++++++++++ src/besapi/__init__.py | 2 +- src/besapi/besapi.py | 26 ++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 examples/get_upload.py diff --git a/examples/get_upload.py b/examples/get_upload.py new file mode 100644 index 0000000..ddec708 --- /dev/null +++ b/examples/get_upload.py @@ -0,0 +1,36 @@ +""" +Get existing upload info by sha1 and filename + +requires `besapi`, install with command `pip install besapi` +""" +import os +import sys + +# the following lines before `import besapi` are only needed when testing local +sys.path.append( + os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "src") +) +# reverse the order so we make sure to get the local src module +sys.path.reverse() + +import besapi + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + result = bes_conn.get_upload("test_besapi_upload.txt", "092bd8ef7b91507bb3848640ef47bb392e7d95b1") + + print(result) + + if result: + print(bes_conn.parse_upload_result_to_prefetch(result)) + print("Info: Upload found.") + else: + print("ERROR: Upload not found!") + + +if __name__ == "__main__": + main() diff --git a/src/besapi/__init__.py b/src/besapi/__init__.py index a137621..3632b6f 100644 --- a/src/besapi/__init__.py +++ b/src/besapi/__init__.py @@ -6,4 +6,4 @@ from . import besapi -__version__ = "3.1.1" +__version__ = "3.1.2" diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 7585fc4..be99736 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -505,6 +505,32 @@ def create_group_from_file(self, bes_file_path, site_path=None): return self.get_computergroup(site_path, new_group_name) + def get_upload(self, file_name, file_hash): + """ + check for a specific file upload reference + + each upload is uniquely identified by sha1 and filename + + - https://developer.bigfix.com/rest-api/api/upload.html + - https://github.com/jgstew/besapi/issues/3 + """ + if len(file_hash) != 40: + raise ValueError("Invalid SHA1 Hash! Must be 40 characters!") + + if " " in file_hash or " " in file_name: + raise ValueError("file name and hash cannot contain spaces") + + if len(file_name) > 0: + result = self.get(self.url("upload/" + file_hash + "/" + file_name)) + else: + raise ValueError("No file_name specified. Must be at least one character.") + + if "Upload not found" in result.text: + # print("WARNING: Upload not found!") + return None + + return result + def upload(self, file_path, file_name=None): """ upload a single file From 10c008efb459ef648392ddb3c5862c32b4aacce2 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 16 Feb 2023 15:59:32 -0500 Subject: [PATCH 028/231] fix formatting --- examples/get_upload.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/examples/get_upload.py b/examples/get_upload.py index ddec708..6a278fc 100644 --- a/examples/get_upload.py +++ b/examples/get_upload.py @@ -3,25 +3,19 @@ requires `besapi`, install with command `pip install besapi` """ -import os -import sys - -# the following lines before `import besapi` are only needed when testing local -sys.path.append( - os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "src") -) -# reverse the order so we make sure to get the local src module -sys.path.reverse() import besapi + def main(): """Execution starts here""" print("main()") bes_conn = besapi.besapi.get_bes_conn_using_config_file() bes_conn.login() - result = bes_conn.get_upload("test_besapi_upload.txt", "092bd8ef7b91507bb3848640ef47bb392e7d95b1") + result = bes_conn.get_upload( + "test_besapi_upload.txt", "092bd8ef7b91507bb3848640ef47bb392e7d95b1" + ) print(result) From 5697afc3cdfc8fe0a141a53a791a2661a092aa8d Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 16 Feb 2023 16:06:37 -0500 Subject: [PATCH 029/231] formatting fixes --- src/besapi/besapi.py | 2 -- src/bescli/bescli.py | 1 - 2 files changed, 3 deletions(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index be99736..15a61fc 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -77,7 +77,6 @@ def elem2dict(node): else: value = elem2dict(element) if key in result: - if type(result[key]) is list: result[key].append(value) else: @@ -196,7 +195,6 @@ class BESConnection: """BigFix RESTAPI connection abstraction class""" def __init__(self, username, password, rootserver, verify=False): - if not verify: # disable SSL warnings requests.packages.urllib3.disable_warnings() # pylint: disable=no-member diff --git a/src/bescli/bescli.py b/src/bescli/bescli.py index 4e70558..d5533c0 100644 --- a/src/bescli/bescli.py +++ b/src/bescli/bescli.py @@ -111,7 +111,6 @@ def do_conf(self, conf_file=None): self.conf_path = found_config_files[0] if self.CONFPARSER: - try: self.BES_ROOT_SERVER = self.CONFPARSER.get("besapi", "BES_ROOT_SERVER") except BaseException: From c7f6aef8b2ffc81d72be7b1d84b19caed24438c1 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 16 Feb 2023 16:07:47 -0500 Subject: [PATCH 030/231] bump for publish --- src/besapi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/__init__.py b/src/besapi/__init__.py index 3632b6f..b924a74 100644 --- a/src/besapi/__init__.py +++ b/src/besapi/__init__.py @@ -6,4 +6,4 @@ from . import besapi -__version__ = "3.1.2" +__version__ = "3.1.3" From 9c9ac678887b11f5fc4fb5869c82904a52672bd9 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 16 Feb 2023 16:31:49 -0500 Subject: [PATCH 031/231] improved upload function implemented --- src/besapi/besapi.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 15a61fc..e8d8588 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -11,6 +11,7 @@ import configparser import datetime +import hashlib import json import logging import os @@ -529,7 +530,7 @@ def get_upload(self, file_name, file_hash): return result - def upload(self, file_path, file_name=None): + def upload(self, file_path, file_name=None, file_hash=None): """ upload a single file https://developer.bigfix.com/rest-api/api/upload.html @@ -542,6 +543,34 @@ def upload(self, file_path, file_name=None): if not file_name: file_name = os.path.basename(file_path) + if not file_hash: + besapi_logger.warning( + "SHA1 hash of file to be uploaded not provided, calculating it." + ) + sha1 = hashlib.sha1() + with open(file_path, "rb") as f: + while True: + # read 64k chunks + data = f.read(65536) + if not data: + break + sha1.update(data) + file_hash = sha1.hexdigest() + + check_upload = None + if file_hash: + try: + check_upload = self.get_upload(str(file_name), str(file_hash)) + except BaseException as err: + print(err) + + if check_upload: + besapi_logger.warning( + "Existing Matching Upload Found, Skipping Upload!" + ) + # return same data as if we had uploaded + return check_upload + # Example Header:: Content-Disposition: attachment; filename="file.xml" headers = {"Content-Disposition": f'attachment; filename="{file_name}"'} with open(file_path, "rb") as f: From 6e302e1997c277fab51341444d5732b5edd2a8fe Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 16 Feb 2023 16:33:33 -0500 Subject: [PATCH 032/231] bump version to publish implemented upload function improvements https://github.com/jgstew/besapi/issues/3 --- src/besapi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/__init__.py b/src/besapi/__init__.py index b924a74..b532a67 100644 --- a/src/besapi/__init__.py +++ b/src/besapi/__init__.py @@ -6,4 +6,4 @@ from . import besapi -__version__ = "3.1.3" +__version__ = "3.1.4" From 04c9fdadccd5d2cb6f9a52b59f6b22973519ec98 Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 13 Mar 2023 12:28:56 -0400 Subject: [PATCH 033/231] proof of concept add mime field --- examples/fixlet_add_mime_field.py | 85 +++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 examples/fixlet_add_mime_field.py diff --git a/examples/fixlet_add_mime_field.py b/examples/fixlet_add_mime_field.py new file mode 100644 index 0000000..c18ab5b --- /dev/null +++ b/examples/fixlet_add_mime_field.py @@ -0,0 +1,85 @@ +""" +Add mime field to custom content + +Need to url escape site name https://bigfix:52311/api/sites +""" +import lxml.etree + +import besapi + +FIXLET_NAME = "Install Microsoft Orca from local SDK - Windows" +MIME_FIELD = "x-relevance-evaluation-period" +session_relevance = ( + 'custom bes fixlets whose(name of it = "' + + FIXLET_NAME + + '" AND not exists mime fields "' + + MIME_FIELD + + '" of it)' +) + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + data = {"relevance": "id of " + session_relevance} + + result = bes_conn.post(bes_conn.url("query"), data) + + # example result: fixlet/custom/Public%2fWindows/21405 + # full example: https://bigfix:52311/api/fixlet/custom/Public%2fWindows/21405 + fixlet_id = int(result.besdict["Query"]["Result"]["Answer"]) + + print(fixlet_id) + + data = {"relevance": "name of site of " + session_relevance} + + result = bes_conn.post(bes_conn.url("query"), data) + + fixlet_site_name = str(result.besdict["Query"]["Result"]["Answer"]) + + fixlet_site_name = fixlet_site_name.replace("/", "%2f") + + print(fixlet_site_name) + + fixlet_content = bes_conn.get_content_by_resource( + f"fixlet/custom/{fixlet_site_name}/{fixlet_id}" + ) + + # print(fixlet_content) + + root_xml = lxml.etree.fromstring(fixlet_content.besxml) + + xml_first_mime = root_xml.find(".//*/MIMEField") + + xml_container = xml_first_mime.getparent() + + print(lxml.etree.tostring(xml_first_mime)) + + print(xml_container.index(xml_first_mime)) + + new_mime = lxml.etree.XML( + """ + x-relevance-evaluation-period + 01:00:00 + """ + ) + + print(lxml.etree.tostring(new_mime)) + + # insert new mime BEFORE first MIME + xml_container.insert(xml_container.index(xml_first_mime) - 1, new_mime) + + print( + lxml.etree.tostring(root_xml, encoding="utf-8", xml_declaration=True).decode( + "utf-8" + ) + ) + + # TODO: PUT changed XML back to RESTAPI resource to modify + + +if __name__ == "__main__": + main() From 3dfc84488c6e8077d4f51f4ee1a79b9cd3ae7252 Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 13 Mar 2023 12:35:51 -0400 Subject: [PATCH 034/231] add comments --- examples/fixlet_add_mime_field.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/examples/fixlet_add_mime_field.py b/examples/fixlet_add_mime_field.py index c18ab5b..1952f22 100644 --- a/examples/fixlet_add_mime_field.py +++ b/examples/fixlet_add_mime_field.py @@ -40,6 +40,8 @@ def main(): fixlet_site_name = str(result.besdict["Query"]["Result"]["Answer"]) + # escape `/` in site name, if applicable + # do spaces need escaped too? `%20` fixlet_site_name = fixlet_site_name.replace("/", "%2f") print(fixlet_site_name) @@ -52,6 +54,7 @@ def main(): root_xml = lxml.etree.fromstring(fixlet_content.besxml) + # get first MIMEField xml_first_mime = root_xml.find(".//*/MIMEField") xml_container = xml_first_mime.getparent() @@ -60,6 +63,7 @@ def main(): print(xml_container.index(xml_first_mime)) + # new mime to set relevance eval to once an hour: new_mime = lxml.etree.XML( """ x-relevance-evaluation-period @@ -70,12 +74,14 @@ def main(): print(lxml.etree.tostring(new_mime)) # insert new mime BEFORE first MIME + # https://stackoverflow.com/questions/7474972/append-element-after-another-element-using-lxml xml_container.insert(xml_container.index(xml_first_mime) - 1, new_mime) print( + "\nPreview of new XML:\n ", lxml.etree.tostring(root_xml, encoding="utf-8", xml_declaration=True).decode( "utf-8" - ) + ), ) # TODO: PUT changed XML back to RESTAPI resource to modify From 7ce1bb12550da41ca950a33895e89dd6c07b32ee Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 10 Apr 2023 15:52:09 -0400 Subject: [PATCH 035/231] example for getting resource uris from session relevance results. --- examples/export_bes_by_relevance.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 examples/export_bes_by_relevance.py diff --git a/examples/export_bes_by_relevance.py b/examples/export_bes_by_relevance.py new file mode 100644 index 0000000..e125db9 --- /dev/null +++ b/examples/export_bes_by_relevance.py @@ -0,0 +1,27 @@ +""" +Example export bes files by session relevance result + +requires `besapi`, install with command `pip install besapi` +""" +import besapi + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + print(bes_conn.last_connected) + + session_relevance = '(type of it as lowercase & "/custom/" & name of site of it & "/" & id of it as string) of custom bes fixlets whose(name of it as lowercase contains "oracle")' + + result = bes_conn.session_relevance_array(session_relevance) + + print(result) + + # TODO: export files + + +if __name__ == "__main__": + main() From d9396619901779960915feec6eea5ed7d686346d Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Apr 2023 09:18:07 -0400 Subject: [PATCH 036/231] Update __init__.py --- src/besapi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/__init__.py b/src/besapi/__init__.py index b532a67..80ae0b4 100644 --- a/src/besapi/__init__.py +++ b/src/besapi/__init__.py @@ -6,4 +6,4 @@ from . import besapi -__version__ = "3.1.4" +__version__ = "3.1.5" From 48a711ec2a9bdcbf90729c08d470df17c86c3f87 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Apr 2023 09:24:59 -0400 Subject: [PATCH 037/231] Update .flake8 stop flake8 from failing the module due to example scripts --- .flake8 | 1 + 1 file changed, 1 insertion(+) diff --git a/.flake8 b/.flake8 index d1de6cc..79e952b 100644 --- a/.flake8 +++ b/.flake8 @@ -18,3 +18,4 @@ ignore = E127, E128, E203, E265, E266, E402, E501, E722, P207, P208, W503 exclude = .git __pycache__ + examples From 3d1a6d9215593d2e0f2f44035c7640ba783b301b Mon Sep 17 00:00:00 2001 From: jgstew Date: Tue, 11 Apr 2023 10:20:15 -0400 Subject: [PATCH 038/231] export bes by relevance --- examples/export_bes_by_relevance.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/export_bes_by_relevance.py b/examples/export_bes_by_relevance.py index e125db9..258ebb9 100644 --- a/examples/export_bes_by_relevance.py +++ b/examples/export_bes_by_relevance.py @@ -18,9 +18,10 @@ def main(): result = bes_conn.session_relevance_array(session_relevance) - print(result) - - # TODO: export files + for item in result: + print(item) + # export bes file: + print(bes_conn.export_item_by_resource(item)) if __name__ == "__main__": From b049f2d1dfe631a283bc454dbd653fe8416718e9 Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 14 Apr 2023 09:53:30 -0400 Subject: [PATCH 039/231] some tweaks, added comments. --- examples/export_bes_by_relevance.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/export_bes_by_relevance.py b/examples/export_bes_by_relevance.py index 258ebb9..f429cd0 100644 --- a/examples/export_bes_by_relevance.py +++ b/examples/export_bes_by_relevance.py @@ -14,7 +14,11 @@ def main(): print(bes_conn.last_connected) - session_relevance = '(type of it as lowercase & "/custom/" & name of site of it & "/" & id of it as string) of custom bes fixlets whose(name of it as lowercase contains "oracle")' + # change the relevance here to adjust which content gets exported: + fixlets_rel = 'custom bes fixlets whose(name of it as lowercase contains "oracle" AND name of it does not contain "VirtualBox")' + + # this does not currently work with things in the actionsite: + session_relevance = f'(type of it as lowercase & "/custom/" & name of site of it & "/" & id of it as string) of {fixlets_rel}' result = bes_conn.session_relevance_array(session_relevance) From 4f35c4c2da04bc8d3ab39544f2922dd3595d05fe Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 27 Apr 2023 14:58:54 -0400 Subject: [PATCH 040/231] add example to import bes file --- examples/import_bes_file.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 examples/import_bes_file.py diff --git a/examples/import_bes_file.py b/examples/import_bes_file.py new file mode 100644 index 0000000..db02522 --- /dev/null +++ b/examples/import_bes_file.py @@ -0,0 +1,26 @@ +""" +import bes file into site + +requires `besapi`, install with command `pip install besapi` +""" + +import besapi + +SITE_PATH = "custom/demo" +BES_FILE_PATH = "examples/example.bes" + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + with open(BES_FILE_PATH) as f: + content = f.read() + result = bes_conn.post(f"import/{SITE_PATH}", content) + print(result) + + +if __name__ == "__main__": + main() From 6883f2e706cdf0bf25b359085454226963a2e887 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 27 Apr 2023 15:00:27 -0400 Subject: [PATCH 041/231] add comment --- examples/import_bes_file.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/import_bes_file.py b/examples/import_bes_file.py index db02522..b0ccbf6 100644 --- a/examples/import_bes_file.py +++ b/examples/import_bes_file.py @@ -1,6 +1,8 @@ """ import bes file into site +- https://developer.bigfix.com/rest-api/api/import.html + requires `besapi`, install with command `pip install besapi` """ @@ -18,6 +20,7 @@ def main(): with open(BES_FILE_PATH) as f: content = f.read() + # https://developer.bigfix.com/rest-api/api/import.html result = bes_conn.post(f"import/{SITE_PATH}", content) print(result) From 1e2a1c99b2e486032c0488a1b33eec16b5db2f7f Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 3 May 2023 13:40:17 -0400 Subject: [PATCH 042/231] added import bes function. Moved validator function. --- .vscode/settings.json | 7 ++-- src/besapi/besapi.py | 74 +++++++++++++++++++++++++++++-------------- src/bescli/bescli.py | 9 ++++++ 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6290b00..1c52634 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,10 +3,13 @@ "python.analysis.diagnosticSeverityOverrides": { "reportMissingImports": "information" }, - "python.formatting.provider": "black", + "python.formatting.provider": "none", "python.autoComplete.addBrackets": true, "editor.formatOnSave": true, "git.autofetch": true, "python.analysis.completeFunctionParens": true, - "python.linting.enabled": true + "python.linting.enabled": true, + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } } diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index e8d8588..9204bef 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -150,6 +150,30 @@ def parse_bes_modtime(string_datetime): # ) +def validate_xsd(doc): + """validate results using XML XSDs""" + try: + xmldoc = etree.fromstring(doc) + except BaseException: + return False + + for xsd in ["BES.xsd", "BESAPI.xsd", "BESActionSettings.xsd"]: + xmlschema_doc = etree.parse(resource_filename(__name__, "schemas/%s" % xsd)) + + # one schema may throw an error while another will validate + try: + xmlschema = etree.XMLSchema(xmlschema_doc) + except etree.XMLSchemaParseError as err: + # this should only error if the XSD itself is malformed + besapi_logger.error("ERROR with `%s`: %s", xsd, err) + raise err + + if xmlschema.validate(xmldoc): + return True + + return False + + def get_bes_conn_using_config_file(conf_file=None): """ read connection values from config file @@ -313,12 +337,12 @@ def session_relevance_array(self, relevance, **kwargs): if "no such child: Answer" in str(err): try: result.append("ERROR: " + rel_result.besobj.Query.Error.text) - except AttributeError as err: - if "no such child: Error" in str(err): + except AttributeError as err2: + if "no such child: Error" in str(err2): result.append(" Nothing returned, but no error.") besapi_logger.info("Query did not return any results") else: - besapi_logger.error("%s\n%s", err, rel_result.text) + besapi_logger.error("%s\n%s", err2, rel_result.text) raise else: raise @@ -430,6 +454,29 @@ def set_current_site_path(self, site_path): self.site_path = site_path return self.site_path + def import_bes_to_site(self, bes_file_path, site_path=None): + """import bes file to site""" + + if not os.access(bes_file_path, os.R_OK): + besapi_logger.error(f"{bes_file_path} is not readable") + raise FileNotFoundError(f"{bes_file_path} is not readable") + + site_path = self.get_current_site_path(site_path) + + self.validate_site_path(site_path, True, True) + + with open(bes_file_path, "rb") as f: + content = f.read() + + # validate BES File contents: + if validate_xsd(content): + # TODO: validate write access to site? + # https://developer.bigfix.com/rest-api/api/import.html + result = self.post(f"import/{site_path}", content) + return result + else: + besapi_logger.error(f"{bes_file_path} is not valid") + def create_site_from_file(self, bes_file_path, site_type="custom"): """create new site""" xml_parsed = etree.parse(bes_file_path) @@ -908,26 +955,7 @@ def besjson(self): def validate_xsd(self, doc): """validate results using XML XSDs""" - try: - xmldoc = etree.fromstring(doc) - except BaseException: - return False - - for xsd in ["BES.xsd", "BESAPI.xsd", "BESActionSettings.xsd"]: - xmlschema_doc = etree.parse(resource_filename(__name__, "schemas/%s" % xsd)) - - # one schema may throw an error while another will validate - try: - xmlschema = etree.XMLSchema(xmlschema_doc) - except etree.XMLSchemaParseError as err: - # this should only error if the XSD itself is malformed - besapi_logger.error("ERROR with `%s`: %s", xsd, err) - raise err - - if xmlschema.validate(xmldoc): - return True - - return False + return validate_xsd(doc) def xmlparse_text(self, text): """parse response text as xml""" diff --git a/src/bescli/bescli.py b/src/bescli/bescli.py index d5533c0..9bc5dd3 100644 --- a/src/bescli/bescli.py +++ b/src/bescli/bescli.py @@ -358,6 +358,15 @@ def do_export_all_sites(self, statement=None): """export site contents to current folder""" self.bes_conn.export_all_sites(verbose=False) + complete_import_bes = Cmd.path_complete + + def do_import_bes(self, statement): + """import bes file""" + + self.poutput(f"Import file: {statement.args}") + + self.poutput(self.bes_conn.import_bes_to_site(str(statement.args))) + complete_upload = Cmd.path_complete def do_upload(self, file_path): From 62c07b4e9943969cbca58d99c3556f8a26182170 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 4 May 2023 06:25:07 -0400 Subject: [PATCH 043/231] pylint fixes, improve import --- src/besapi/__init__.py | 2 +- src/besapi/besapi.py | 60 ++++++++++++++++++++++++------------------ 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/src/besapi/__init__.py b/src/besapi/__init__.py index 80ae0b4..c84f962 100644 --- a/src/besapi/__init__.py +++ b/src/besapi/__init__.py @@ -6,4 +6,4 @@ from . import besapi -__version__ = "3.1.5" +__version__ = "3.1.6" diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 9204bef..e86feb9 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -12,6 +12,7 @@ import configparser import datetime import hashlib +import io import json import logging import os @@ -154,7 +155,7 @@ def validate_xsd(doc): """validate results using XML XSDs""" try: xmldoc = etree.fromstring(doc) - except BaseException: + except BaseException: # pylint: disable=broad-except return False for xsd in ["BES.xsd", "BESAPI.xsd", "BESActionSettings.xsd"]: @@ -197,17 +198,17 @@ def get_bes_conn_using_config_file(conf_file=None): print("Attempting BESAPI Connection using config file:", found_config_files) try: BES_ROOT_SERVER = configparser_instance.get("besapi", "BES_ROOT_SERVER") - except BaseException: + except BaseException: # pylint: disable=broad-except BES_ROOT_SERVER = None try: BES_USER_NAME = configparser_instance.get("besapi", "BES_USER_NAME") - except BaseException: + except BaseException: # pylint: disable=broad-except BES_USER_NAME = None try: BES_PASSWORD = configparser_instance.get("besapi", "BES_PASSWORD") - except BaseException: + except BaseException: # pylint: disable=broad-except BES_PASSWORD = None if BES_ROOT_SERVER and BES_USER_NAME and BES_PASSWORD: @@ -246,7 +247,7 @@ def __init__(self, username, password, rootserver, verify=False): try: # get root server port self.rootserver_port = int(rootserver.split("://", 1)[1].split(":", 1)[1]) - except BaseException: + except BaseException: # pylint: disable=broad-except # if error, assume default self.rootserver_port = 52311 @@ -414,18 +415,18 @@ def validate_site_path(self, site_path, check_site_exists=True, raise_error=Fals if not check_site_exists: # don't check if site exists first return site_path - else: - # check site exists first - site_result = self.get(f"site/{site_path}") - if site_result.request.status_code != 200: - besapi_logger.info("Site `%s` does not exist", site_path) - if not raise_error: - return None - raise ValueError(f"Site at path `{site_path}` does not exist!") + # check site exists first + site_result = self.get(f"site/{site_path}") + if site_result.request.status_code != 200: + besapi_logger.info("Site `%s` does not exist", site_path) + if not raise_error: + return None - # site_path is valid and exists: - return site_path + raise ValueError(f"Site at path `{site_path}` does not exist!") + + # site_path is valid and exists: + return site_path # Invalid: No valid prefix found raise ValueError( @@ -458,24 +459,24 @@ def import_bes_to_site(self, bes_file_path, site_path=None): """import bes file to site""" if not os.access(bes_file_path, os.R_OK): - besapi_logger.error(f"{bes_file_path} is not readable") + besapi_logger.error("%s is not readable", bes_file_path) raise FileNotFoundError(f"{bes_file_path} is not readable") site_path = self.get_current_site_path(site_path) - self.validate_site_path(site_path, True, True) + self.validate_site_path(site_path, False, True) with open(bes_file_path, "rb") as f: content = f.read() # validate BES File contents: - if validate_xsd(content): - # TODO: validate write access to site? - # https://developer.bigfix.com/rest-api/api/import.html - result = self.post(f"import/{site_path}", content) - return result - else: - besapi_logger.error(f"{bes_file_path} is not valid") + if not validate_xsd(content): + besapi_logger.error("%s is not valid", bes_file_path) + return None + + # https://developer.bigfix.com/rest-api/api/import.html + result = self.post(f"import/{site_path}", content) + return result def create_site_from_file(self, bes_file_path, site_type="custom"): """create new site""" @@ -608,7 +609,7 @@ def upload(self, file_path, file_name=None, file_hash=None): if file_hash: try: check_upload = self.get_upload(str(file_name), str(file_hash)) - except BaseException as err: + except BaseException as err: # pylint: disable=broad-except print(err) if check_upload: @@ -668,6 +669,13 @@ def update_item_from_file(self, file_path, site_path=None): """update an item by name and last modified""" site_path = self.get_current_site_path(site_path) bes_tree = etree.parse(file_path) + + with open(file_path, "rb") as f: + content = f.read() + if not validate_xsd(content): + besapi_logger.error("%s is not valid", file_path) + return None + # get name of first child tag of BES # - https://stackoverflow.com/a/3601919/861745 bes_type = str(bes_tree.xpath("name(/BES/*[1])")) @@ -910,7 +918,7 @@ def __str__(self): # I think this is needed for python3 compatibility: try: return self.besxml.decode("utf-8") - except BaseException: + except BaseException: # pylint: disable=broad-except return self.besxml else: return self.text From c1f74e0da6265f44b3e3ba57dc3285d8eed354cb Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 4 May 2023 06:40:33 -0400 Subject: [PATCH 044/231] tweak action --- .github/workflows/tag_and_release.yaml | 4 ++-- examples/import_bes_file.py | 9 ++++----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tag_and_release.yaml b/.github/workflows/tag_and_release.yaml index ba7e1b7..624b075 100644 --- a/.github/workflows/tag_and_release.yaml +++ b/.github/workflows/tag_and_release.yaml @@ -35,7 +35,7 @@ jobs: # what if no other tests? - name: Wait for tests to succeed if: steps.tagged.outputs.tagged == 1 - uses: lewagon/wait-on-check-action@v0.2 + uses: lewagon/wait-on-check-action@v1.3.1 with: ref: master running-workflow-name: "Tag and Release" @@ -66,6 +66,6 @@ jobs: ${{ steps.getwheelfile.outputs.wheelfile }} - name: Publish distribution to PyPI if: steps.tagged.outputs.tagged == 1 - uses: pypa/gh-action-pypi-publish@master + uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/examples/import_bes_file.py b/examples/import_bes_file.py index b0ccbf6..3b0525a 100644 --- a/examples/import_bes_file.py +++ b/examples/import_bes_file.py @@ -18,11 +18,10 @@ def main(): bes_conn = besapi.besapi.get_bes_conn_using_config_file() bes_conn.login() - with open(BES_FILE_PATH) as f: - content = f.read() - # https://developer.bigfix.com/rest-api/api/import.html - result = bes_conn.post(f"import/{SITE_PATH}", content) - print(result) + # requires besapi 3.1.6 + result = bes_conn.import_bes_to_site(BES_FILE_PATH, SITE_PATH) + + print(result) if __name__ == "__main__": From 4856f2b37f4ab3b5ec0d5d13b8ab1a8c64a21e1b Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 4 May 2023 06:46:15 -0400 Subject: [PATCH 045/231] exclude examples folder --- .github/workflows/test_build.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index 88472a6..ac5ff74 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -4,7 +4,8 @@ name: test_build on: push: paths: - - "**.py" + - "src/**.py" + - "tests/**.py" - "setup.cfg" - "MANIFEST.in" - "pyproject.toml" @@ -13,7 +14,8 @@ on: - ".github/workflows/tag_and_release.yaml" pull_request: paths: - - "**.py" + - "src/**.py" + - "tests/**.py" - "setup.cfg" - "MANIFEST.in" - "pyproject.toml" From 976d9881f2de3e65558b5f6011105da44eab2f91 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 4 May 2023 06:47:45 -0400 Subject: [PATCH 046/231] exclude self-run --- .github/workflows/tag_and_release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tag_and_release.yaml b/.github/workflows/tag_and_release.yaml index 624b075..12b71ed 100644 --- a/.github/workflows/tag_and_release.yaml +++ b/.github/workflows/tag_and_release.yaml @@ -7,7 +7,7 @@ on: - master paths: - "src/besapi/__init__.py" - - ".github/workflows/tag_and_release.yaml" + # - ".github/workflows/tag_and_release.yaml" jobs: release_new_tag: From 0e72eb8b51847cf572b420f3793342fc3e823579 Mon Sep 17 00:00:00 2001 From: JGStew Date: Mon, 8 May 2023 13:28:57 -0400 Subject: [PATCH 047/231] add script to import multiple files --- examples/import_bes_files.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 examples/import_bes_files.py diff --git a/examples/import_bes_files.py b/examples/import_bes_files.py new file mode 100644 index 0000000..91fd9af --- /dev/null +++ b/examples/import_bes_files.py @@ -0,0 +1,34 @@ +""" +import bes file into site + +- https://developer.bigfix.com/rest-api/api/import.html + +requires `besapi`, install with command `pip install besapi` +""" + +import glob + +import besapi + +SITE_PATH = "custom/demo" + +# by default, get all BES files in examples folder: +BES_FOLDER_GLOB = "./examples/*.bes" + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + files = glob.glob(BES_FOLDER_GLOB) + # import all found BES files into site: + for f in files: + # requires besapi 3.1.6 + result = bes_conn.import_bes_to_site(f, SITE_PATH) + print(result) + + +if __name__ == "__main__": + main() From b96569b0f6d8ef3a55889ce8032a1a532a67cf7f Mon Sep 17 00:00:00 2001 From: JGStew Date: Mon, 8 May 2023 13:47:02 -0400 Subject: [PATCH 048/231] minor tweaks to examples --- examples/import_bes_file.py | 7 +++++++ examples/import_bes_files.py | 18 +++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/examples/import_bes_file.py b/examples/import_bes_file.py index 3b0525a..f6abee4 100644 --- a/examples/import_bes_file.py +++ b/examples/import_bes_file.py @@ -15,6 +15,13 @@ def main(): """Execution starts here""" print("main()") + + print(f"besapi version: { besapi.__version__ }") + + if not hasattr(besapi.besapi.BESConnection, "import_bes_to_site"): + print("version of besapi is too old, must be >= 3.1.6") + return None + bes_conn = besapi.besapi.get_bes_conn_using_config_file() bes_conn.login() diff --git a/examples/import_bes_files.py b/examples/import_bes_files.py index 91fd9af..5de41c7 100644 --- a/examples/import_bes_files.py +++ b/examples/import_bes_files.py @@ -19,10 +19,22 @@ def main(): """Execution starts here""" print("main()") - bes_conn = besapi.besapi.get_bes_conn_using_config_file() - bes_conn.login() + + print(f"besapi version: { besapi.__version__ }") + + if not hasattr(besapi.besapi.BESConnection, "import_bes_to_site"): + print("version of besapi is too old, must be >= 3.1.6") + return None files = glob.glob(BES_FOLDER_GLOB) + + if len(files) > 0: + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + else: + print(f"No BES Files found using glob: {BES_FOLDER_GLOB}") + return None + # import all found BES files into site: for f in files: # requires besapi 3.1.6 @@ -31,4 +43,4 @@ def main(): if __name__ == "__main__": - main() + print(main()) From 3083cf1ff28c2bd521d5e24e8468878341c4b942 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 10 May 2023 15:28:47 -0400 Subject: [PATCH 049/231] add output when about to import file --- examples/import_bes_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/import_bes_files.py b/examples/import_bes_files.py index 5de41c7..1573bde 100644 --- a/examples/import_bes_files.py +++ b/examples/import_bes_files.py @@ -37,6 +37,7 @@ def main(): # import all found BES files into site: for f in files: + print(f"Importing file: {f}") # requires besapi 3.1.6 result = bes_conn.import_bes_to_site(f, SITE_PATH) print(result) From c1dbd58edf1709cd83779148a9e575a099d93706 Mon Sep 17 00:00:00 2001 From: jgstew Date: Tue, 23 May 2023 22:29:59 -0400 Subject: [PATCH 050/231] example to create baseline based upon sesssion relevance result --- examples/baseline_by_relevance.py | 60 +++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 examples/baseline_by_relevance.py diff --git a/examples/baseline_by_relevance.py b/examples/baseline_by_relevance.py new file mode 100644 index 0000000..710bf80 --- /dev/null +++ b/examples/baseline_by_relevance.py @@ -0,0 +1,60 @@ +""" +create baseline by session relevance result + +requires `besapi`, install with command `pip install besapi` +""" +import besapi + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + print(bes_conn.last_connected) + + # change the relevance here to adjust which content gets put in a baseline: + fixlets_rel = 'fixlets whose(name of it starts with "Update:") of bes sites whose( external site flag of it AND name of it = "Updates for Windows Applications Extended" )' + + # this does not currently work with things in the actionsite: + session_relevance = f"""(it as string) of (url of site of it, ids of it, content id of default action of it) of it whose(exists default action of it AND globally visible flag of it AND name of it does not contain "(Superseded)" AND exists applicable computers of it) of {fixlets_rel}""" + + result = bes_conn.session_relevance_array(session_relevance) + + # print(result) + + baseline_components = "" + + for item in result: + # print(item) + tuple_items = item.split(", ") + baseline_components += f""" + """ + break + + # print(baseline_components) + + baseline = f""" + + + Custom Patching Baseline + + true + + {baseline_components} + + + +""" + + print(baseline) + + with open("baseline.bes", "w") as f: + f.write(baseline) + + print("WARNING: Work In Progress, resulting baseline is not yet correct") + + +if __name__ == "__main__": + main() From aa79075e7d41ccf558e6774bd2dbd91ca54229f7 Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 24 May 2023 09:28:50 -0400 Subject: [PATCH 051/231] working baseline by relevance example --- examples/baseline_by_relevance.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/examples/baseline_by_relevance.py b/examples/baseline_by_relevance.py index 710bf80..427a318 100644 --- a/examples/baseline_by_relevance.py +++ b/examples/baseline_by_relevance.py @@ -3,6 +3,8 @@ requires `besapi`, install with command `pip install besapi` """ +import os + import besapi @@ -17,8 +19,8 @@ def main(): # change the relevance here to adjust which content gets put in a baseline: fixlets_rel = 'fixlets whose(name of it starts with "Update:") of bes sites whose( external site flag of it AND name of it = "Updates for Windows Applications Extended" )' - # this does not currently work with things in the actionsite: - session_relevance = f"""(it as string) of (url of site of it, ids of it, content id of default action of it) of it whose(exists default action of it AND globally visible flag of it AND name of it does not contain "(Superseded)" AND exists applicable computers of it) of {fixlets_rel}""" + # this gets the info needed from the items to make the baseline: + session_relevance = f"""(it as string) of (url of site of it, ids of it, content id of default action of it | "Action1") of it whose(exists default action of it AND globally visible flag of it AND name of it does not contain "(Superseded)" AND exists applicable computers of it) of {fixlets_rel}""" result = bes_conn.session_relevance_array(session_relevance) @@ -31,7 +33,6 @@ def main(): tuple_items = item.split(", ") baseline_components += f""" """ - break # print(baseline_components) @@ -48,12 +49,22 @@ def main(): """ - print(baseline) + # print(baseline) + + file_path = "tmp_baseline.bes" - with open("baseline.bes", "w") as f: + # Does not work through console import: + with open(file_path, "w") as f: f.write(baseline) - print("WARNING: Work In Progress, resulting baseline is not yet correct") + print("Importing generated baseline...") + import_result = bes_conn.import_bes_to_site(file_path, "custom/autopkg") + + print(import_result) + + os.remove(file_path) + + print("Finished") if __name__ == "__main__": From 3977a026c0f79f2636ba1fa2e413a10dcf809292 Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 24 May 2023 09:31:00 -0400 Subject: [PATCH 052/231] update site ref --- examples/baseline_by_relevance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/baseline_by_relevance.py b/examples/baseline_by_relevance.py index 427a318..f5f5046 100644 --- a/examples/baseline_by_relevance.py +++ b/examples/baseline_by_relevance.py @@ -58,7 +58,7 @@ def main(): f.write(baseline) print("Importing generated baseline...") - import_result = bes_conn.import_bes_to_site(file_path, "custom/autopkg") + import_result = bes_conn.import_bes_to_site(file_path, "custom/Demo") print(import_result) From ca301b034355fa97d86b5348a6753dc2973fb4e5 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 25 May 2023 19:25:26 -0400 Subject: [PATCH 053/231] minor tweaks --- examples/baseline_by_relevance.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/baseline_by_relevance.py b/examples/baseline_by_relevance.py index f5f5046..3ee0fd1 100644 --- a/examples/baseline_by_relevance.py +++ b/examples/baseline_by_relevance.py @@ -3,6 +3,7 @@ requires `besapi`, install with command `pip install besapi` """ +import datetime import os import besapi @@ -22,7 +23,9 @@ def main(): # this gets the info needed from the items to make the baseline: session_relevance = f"""(it as string) of (url of site of it, ids of it, content id of default action of it | "Action1") of it whose(exists default action of it AND globally visible flag of it AND name of it does not contain "(Superseded)" AND exists applicable computers of it) of {fixlets_rel}""" + print("getting items to add to baseline...") result = bes_conn.session_relevance_array(session_relevance) + print(f"{len(result)} items found") # print(result) @@ -39,7 +42,7 @@ def main(): baseline = f""" - Custom Patching Baseline + Custom Patching Baseline {datetime.datetime.today().strftime('%Y-%m-%d')} true From bcebdfceeac2e1d751dc126f3ad8d82b6bced1e7 Mon Sep 17 00:00:00 2001 From: jgstew Date: Sat, 27 May 2023 14:07:30 -0400 Subject: [PATCH 054/231] add ability to automatically create an offer action from generated baseline --- examples/baseline_by_relevance.py | 43 +++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/examples/baseline_by_relevance.py b/examples/baseline_by_relevance.py index 3ee0fd1..21938d4 100644 --- a/examples/baseline_by_relevance.py +++ b/examples/baseline_by_relevance.py @@ -39,6 +39,7 @@ def main(): # print(baseline_components) + # generate XML for baseline with template: baseline = f""" @@ -55,19 +56,57 @@ def main(): # print(baseline) file_path = "tmp_baseline.bes" + site_name = "Demo" + site_path = f"custom/{site_name}" # Does not work through console import: with open(file_path, "w") as f: f.write(baseline) print("Importing generated baseline...") - import_result = bes_conn.import_bes_to_site(file_path, "custom/Demo") + import_result = bes_conn.import_bes_to_site(file_path, site_path) print(import_result) os.remove(file_path) - print("Finished") + # to automatically create an offer action, comment out the next line: + return True + + baseline_id = import_result.besobj.Baseline.ID + + print("creating baseline offer action...") + + BES_SourcedFixletAction = f"""\ + + + + {site_name} + {baseline_id} + Action1 + + + true + + + true + P10D + true + + true + false + Testing + + + + +""" + + action_result = bes_conn.post("actions", BES_SourcedFixletAction) + + print(action_result) + + print("Finished!") if __name__ == "__main__": From 61018309e6b964184afbb3f7b180778cf72c3c23 Mon Sep 17 00:00:00 2001 From: jgstew Date: Sat, 27 May 2023 17:37:37 -0400 Subject: [PATCH 055/231] add an async test, etc. --- .gitignore | 2 + examples/export_bes_by_relevance.py | 9 +++- examples/export_bes_by_relevance_trio.py | 52 ++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 examples/export_bes_by_relevance_trio.py diff --git a/.gitignore b/.gitignore index 71bc149..467a361 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ besapi.conf +tmp/ + .DS_Store # Byte-compiled / optimized / DLL files diff --git a/examples/export_bes_by_relevance.py b/examples/export_bes_by_relevance.py index f429cd0..940aced 100644 --- a/examples/export_bes_by_relevance.py +++ b/examples/export_bes_by_relevance.py @@ -3,6 +3,8 @@ requires `besapi`, install with command `pip install besapi` """ +import time + import besapi @@ -25,8 +27,13 @@ def main(): for item in result: print(item) # export bes file: - print(bes_conn.export_item_by_resource(item)) + print(bes_conn.export_item_by_resource(item, "./tmp/")) if __name__ == "__main__": + # Start the timer + start_time = time.time() main() + # Calculate the elapsed time + elapsed_time = time.time() - start_time + print(f"Execution time: {elapsed_time:.2f} seconds") diff --git a/examples/export_bes_by_relevance_trio.py b/examples/export_bes_by_relevance_trio.py new file mode 100644 index 0000000..e907742 --- /dev/null +++ b/examples/export_bes_by_relevance_trio.py @@ -0,0 +1,52 @@ +""" +Example export bes files by session relevance result + +requires `besapi`, install with command `pip install besapi` +requires `trio`, install with command `pip install trio` +""" +import time + +import trio +import besapi + + +async def async_iterator(lst): + # Create an async generator that yields items from the list + for item in lst: + yield item + + +async def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + print(bes_conn.last_connected) + + # change the relevance here to adjust which content gets exported: + fixlets_rel = 'custom bes fixlets whose(name of it as lowercase contains "oracle" AND name of it does not contain "VirtualBox")' + + # this does not currently work with things in the actionsite: + session_relevance = f'(type of it as lowercase & "/custom/" & name of site of it & "/" & id of it as string) of {fixlets_rel}' + + result = bes_conn.session_relevance_array(session_relevance) + + # constrain the maximum concurrency: + sem = trio.Semaphore(2, max_value=2) + + async with sem: + async for item in async_iterator(result): + print(item) + + # export bes file: + bes_conn.export_item_by_resource(item, "./tmp/") + + +if __name__ == "__main__": + # Start the timer + start_time = time.time() + trio.run(main) + # Calculate the elapsed time + elapsed_time = time.time() - start_time + print(f"Execution time: {elapsed_time:.2f} seconds") From 7bf11df7c93d9bbe2f942778087348c8eb25f5c7 Mon Sep 17 00:00:00 2001 From: jgstew Date: Sat, 27 May 2023 18:59:27 -0400 Subject: [PATCH 056/231] export using threads for multiprocessing --- examples/export_bes_by_relevance_threads.py | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 examples/export_bes_by_relevance_threads.py diff --git a/examples/export_bes_by_relevance_threads.py b/examples/export_bes_by_relevance_threads.py new file mode 100644 index 0000000..3513c30 --- /dev/null +++ b/examples/export_bes_by_relevance_threads.py @@ -0,0 +1,43 @@ +""" +Example export bes files by session relevance result + +requires `besapi`, install with command `pip install besapi` +""" +import time +import concurrent.futures + +import besapi + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + print(bes_conn.last_connected) + + # change the relevance here to adjust which content gets exported: + fixlets_rel = 'custom bes fixlets whose(name of it as lowercase contains "oracle" AND name of it does not contain "VirtualBox")' + + # this does not currently work with things in the actionsite: + session_relevance = f'(type of it as lowercase & "/custom/" & name of site of it & "/" & id of it as string) of {fixlets_rel}' + + result = bes_conn.session_relevance_array(session_relevance) + + with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: + futures = [ + executor.submit(bes_conn.export_item_by_resource, item, "./tmp/") + for item in result + ] + # Wait for all tasks to complete + concurrent.futures.wait(futures) + + +if __name__ == "__main__": + # Start the timer + start_time = time.time() + main() + # Calculate the elapsed time + elapsed_time = time.time() - start_time + print(f"Execution time: {elapsed_time:.2f} seconds") From 7017a54e5ef1d8b9412787ce6f7de817cd12061e Mon Sep 17 00:00:00 2001 From: jgstew Date: Sat, 27 May 2023 19:00:27 -0400 Subject: [PATCH 057/231] trio async doesn't seem to work --- examples/export_bes_by_relevance_trio.py | 52 ------------------------ 1 file changed, 52 deletions(-) delete mode 100644 examples/export_bes_by_relevance_trio.py diff --git a/examples/export_bes_by_relevance_trio.py b/examples/export_bes_by_relevance_trio.py deleted file mode 100644 index e907742..0000000 --- a/examples/export_bes_by_relevance_trio.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -Example export bes files by session relevance result - -requires `besapi`, install with command `pip install besapi` -requires `trio`, install with command `pip install trio` -""" -import time - -import trio -import besapi - - -async def async_iterator(lst): - # Create an async generator that yields items from the list - for item in lst: - yield item - - -async def main(): - """Execution starts here""" - print("main()") - bes_conn = besapi.besapi.get_bes_conn_using_config_file() - bes_conn.login() - - print(bes_conn.last_connected) - - # change the relevance here to adjust which content gets exported: - fixlets_rel = 'custom bes fixlets whose(name of it as lowercase contains "oracle" AND name of it does not contain "VirtualBox")' - - # this does not currently work with things in the actionsite: - session_relevance = f'(type of it as lowercase & "/custom/" & name of site of it & "/" & id of it as string) of {fixlets_rel}' - - result = bes_conn.session_relevance_array(session_relevance) - - # constrain the maximum concurrency: - sem = trio.Semaphore(2, max_value=2) - - async with sem: - async for item in async_iterator(result): - print(item) - - # export bes file: - bes_conn.export_item_by_resource(item, "./tmp/") - - -if __name__ == "__main__": - # Start the timer - start_time = time.time() - trio.run(main) - # Calculate the elapsed time - elapsed_time = time.time() - start_time - print(f"Execution time: {elapsed_time:.2f} seconds") From 585281f12291bff3470f470a3deeaa680e40d1ea Mon Sep 17 00:00:00 2001 From: jgstew Date: Sun, 28 May 2023 11:07:16 -0400 Subject: [PATCH 058/231] working async exporting --- examples/export_bes_by_relevance_async.py | 125 ++++++++++++++++++++ examples/export_bes_by_relevance_threads.py | 2 +- 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 examples/export_bes_by_relevance_async.py diff --git a/examples/export_bes_by_relevance_async.py b/examples/export_bes_by_relevance_async.py new file mode 100644 index 0000000..a4aa3aa --- /dev/null +++ b/examples/export_bes_by_relevance_async.py @@ -0,0 +1,125 @@ +""" +Example export bes files by session relevance result + +requires `besapi`, install with command `pip install besapi` +""" +import asyncio +import configparser +import os +import time + +import aiofiles +import aiohttp + +import besapi + + +def get_bes_pass_using_config_file(conf_file=None): + """ + read connection values from config file + return besapi connection + """ + config_paths = [ + "/etc/besapi.conf", + os.path.expanduser("~/besapi.conf"), + os.path.expanduser("~/.besapi.conf"), + "besapi.conf", + ] + # if conf_file specified, then only use that: + if conf_file: + config_paths = [conf_file] + + configparser_instance = configparser.ConfigParser() + + found_config_files = configparser_instance.read(config_paths) + + if found_config_files and configparser_instance: + print("Attempting BESAPI Connection using config file:", found_config_files) + + try: + BES_PASSWORD = configparser_instance.get("besapi", "BES_PASSWORD") + except BaseException: # pylint: disable=broad-except + BES_PASSWORD = None + + return BES_PASSWORD + + +async def fetch(session, url): + """get items async""" + async with session.get(url) as response: + response_text = await response.text() + + # Extract the filename from the URL + filename = url.split("/")[-1] + + filename = "./tmp/" + filename + + # Write the response to a file asynchronously + async with aiofiles.open(filename, "w") as file: + await file.write(response_text) + + print(f"{filename} downloaded and saved.") + + +async def main(): + """Execution starts here""" + print("main()") + + # Create a semaphore with a maximum concurrent requests + semaphore = asyncio.Semaphore(2) + + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + print(bes_conn.last_connected) + + # change the relevance here to adjust which content gets exported: + fixlets_rel = 'fixlets whose(name of it starts with "Update") of bes custom sites whose(name of it = "autopkg")' + + # this does not currently work with things in the actionsite: + session_relevance = f'(type of it as lowercase & "/custom/" & name of site of it & "/" & id of it as string) of {fixlets_rel}' + + result = bes_conn.session_relevance_array(session_relevance) + + absolute_urls = [] + + for item in result: + absolute_urls.append(bes_conn.url(item)) + + # Create a session for making HTTP requests + async with aiohttp.ClientSession( + auth=aiohttp.BasicAuth(bes_conn.username, get_bes_pass_using_config_file()), + connector=aiohttp.TCPConnector(ssl=False), + ) as session: + # Define a list of URLs to fetch + urls = absolute_urls + + # Create a list to store the coroutines for fetching the URLs + tasks = [] + + # Create coroutines for fetching each URL + for url in urls: + # Acquire the semaphore before starting the request + async with semaphore: + task = asyncio.ensure_future(fetch(session, url)) + tasks.append(task) + + # Wait for all the coroutines to complete + await asyncio.gather(*tasks) + + # # Process the responses + # for response in responses: + # print(response) + + +if __name__ == "__main__": + # Start the timer + start_time = time.time() + + # Run the main function + loop = asyncio.get_event_loop() + loop.run_until_complete(main()) + + # Calculate the elapsed time + elapsed_time = time.time() - start_time + print(f"Execution time: {elapsed_time:.2f} seconds") diff --git a/examples/export_bes_by_relevance_threads.py b/examples/export_bes_by_relevance_threads.py index 3513c30..05c8ab5 100644 --- a/examples/export_bes_by_relevance_threads.py +++ b/examples/export_bes_by_relevance_threads.py @@ -3,8 +3,8 @@ requires `besapi`, install with command `pip install besapi` """ -import time import concurrent.futures +import time import besapi From 7e244e398fdf8467a0cca8e2c1e074d8127032e4 Mon Sep 17 00:00:00 2001 From: jgstew Date: Sun, 28 May 2023 11:32:25 -0400 Subject: [PATCH 059/231] tweak async --- examples/export_bes_by_relevance_async.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/export_bes_by_relevance_async.py b/examples/export_bes_by_relevance_async.py index a4aa3aa..91dcb81 100644 --- a/examples/export_bes_by_relevance_async.py +++ b/examples/export_bes_by_relevance_async.py @@ -50,9 +50,13 @@ async def fetch(session, url): response_text = await response.text() # Extract the filename from the URL - filename = url.split("/")[-1] + url_parts = url.split("/") - filename = "./tmp/" + filename + file_dir = "./tmp/" + url_parts[-2] + "/" + url_parts[-4] + + os.makedirs(file_dir, exist_ok=True) + + filename = file_dir + "/" + url_parts[-1] + ".bes" # Write the response to a file asynchronously async with aiofiles.open(filename, "w") as file: @@ -66,7 +70,7 @@ async def main(): print("main()") # Create a semaphore with a maximum concurrent requests - semaphore = asyncio.Semaphore(2) + semaphore = asyncio.Semaphore(4) bes_conn = besapi.besapi.get_bes_conn_using_config_file() bes_conn.login() @@ -107,10 +111,6 @@ async def main(): # Wait for all the coroutines to complete await asyncio.gather(*tasks) - # # Process the responses - # for response in responses: - # print(response) - if __name__ == "__main__": # Start the timer From 34658f13b340fff02777d7cb644fbff0c3114b5c Mon Sep 17 00:00:00 2001 From: jgstew Date: Sun, 28 May 2023 11:36:00 -0400 Subject: [PATCH 060/231] tweak examples --- examples/export_bes_by_relevance_async.py | 4 ++-- examples/export_bes_by_relevance_threads.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/export_bes_by_relevance_async.py b/examples/export_bes_by_relevance_async.py index 91dcb81..421c727 100644 --- a/examples/export_bes_by_relevance_async.py +++ b/examples/export_bes_by_relevance_async.py @@ -70,7 +70,7 @@ async def main(): print("main()") # Create a semaphore with a maximum concurrent requests - semaphore = asyncio.Semaphore(4) + semaphore = asyncio.Semaphore(3) bes_conn = besapi.besapi.get_bes_conn_using_config_file() bes_conn.login() @@ -78,7 +78,7 @@ async def main(): print(bes_conn.last_connected) # change the relevance here to adjust which content gets exported: - fixlets_rel = 'fixlets whose(name of it starts with "Update") of bes custom sites whose(name of it = "autopkg")' + fixlets_rel = 'custom bes fixlets whose(name of it as lowercase contains "oracle")' # this does not currently work with things in the actionsite: session_relevance = f'(type of it as lowercase & "/custom/" & name of site of it & "/" & id of it as string) of {fixlets_rel}' diff --git a/examples/export_bes_by_relevance_threads.py b/examples/export_bes_by_relevance_threads.py index 05c8ab5..7881cbb 100644 --- a/examples/export_bes_by_relevance_threads.py +++ b/examples/export_bes_by_relevance_threads.py @@ -18,7 +18,7 @@ def main(): print(bes_conn.last_connected) # change the relevance here to adjust which content gets exported: - fixlets_rel = 'custom bes fixlets whose(name of it as lowercase contains "oracle" AND name of it does not contain "VirtualBox")' + fixlets_rel = 'custom bes fixlets whose(name of it as lowercase contains "oracle")' # this does not currently work with things in the actionsite: session_relevance = f'(type of it as lowercase & "/custom/" & name of site of it & "/" & id of it as string) of {fixlets_rel}' From a9ae98b84bdeb8d7211ce5605a1bbf9a40ad2864 Mon Sep 17 00:00:00 2001 From: jgstew Date: Sun, 28 May 2023 11:37:14 -0400 Subject: [PATCH 061/231] make the same --- examples/export_bes_by_relevance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/export_bes_by_relevance.py b/examples/export_bes_by_relevance.py index 940aced..3f2a78a 100644 --- a/examples/export_bes_by_relevance.py +++ b/examples/export_bes_by_relevance.py @@ -17,7 +17,7 @@ def main(): print(bes_conn.last_connected) # change the relevance here to adjust which content gets exported: - fixlets_rel = 'custom bes fixlets whose(name of it as lowercase contains "oracle" AND name of it does not contain "VirtualBox")' + fixlets_rel = 'custom bes fixlets whose(name of it as lowercase contains "oracle")' # this does not currently work with things in the actionsite: session_relevance = f'(type of it as lowercase & "/custom/" & name of site of it & "/" & id of it as string) of {fixlets_rel}' From f16d57594c131d7bf7d330a08457ec08d61cceef Mon Sep 17 00:00:00 2001 From: jgstew Date: Sun, 28 May 2023 13:26:46 -0400 Subject: [PATCH 062/231] tweak --- examples/export_bes_by_relevance_async.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/export_bes_by_relevance_async.py b/examples/export_bes_by_relevance_async.py index 421c727..18dd83a 100644 --- a/examples/export_bes_by_relevance_async.py +++ b/examples/export_bes_by_relevance_async.py @@ -72,6 +72,9 @@ async def main(): # Create a semaphore with a maximum concurrent requests semaphore = asyncio.Semaphore(3) + # TODO: get max mod time of existing bes files: + # https://github.com/jgstew/tools/blob/master/Python/get_max_time_bes_files.py + bes_conn = besapi.besapi.get_bes_conn_using_config_file() bes_conn.login() @@ -85,6 +88,8 @@ async def main(): result = bes_conn.session_relevance_array(session_relevance) + print(f"{len(result)} items to export...") + absolute_urls = [] for item in result: From 223073f2e8ae2dd52c9c0f27b2bb596dd4ade1f1 Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 7 Jul 2023 08:50:23 -0400 Subject: [PATCH 063/231] add example --- examples/delete_task_by_id.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 examples/delete_task_by_id.py diff --git a/examples/delete_task_by_id.py b/examples/delete_task_by_id.py new file mode 100644 index 0000000..000dce6 --- /dev/null +++ b/examples/delete_task_by_id.py @@ -0,0 +1,30 @@ +""" +delete tasks by id +- https://developer.bigfix.com/rest-api/api/task.html + +requires `besapi`, install with command `pip install besapi` +""" + +import besapi + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + ids = [0, "0"] + + # https://developer.bigfix.com/rest-api/api/task.html + # task/{site type}/{site name}/{task id} + + for id in ids: + rest_url = f"task/custom/CUSTOM_SITE_NAME/{int(id)}" + print(f"Deleting: {rest_url}") + result = bes_conn.delete(rest_url) + print(result.text) + + +if __name__ == "__main__": + main() From 0947b6f31454bd1fd70af7e6accb9f13a46896a7 Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 11 Aug 2023 12:54:43 -0400 Subject: [PATCH 064/231] list mailbox files --- examples/mailbox_files_list.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 examples/mailbox_files_list.py diff --git a/examples/mailbox_files_list.py b/examples/mailbox_files_list.py new file mode 100644 index 0000000..ef94f40 --- /dev/null +++ b/examples/mailbox_files_list.py @@ -0,0 +1,29 @@ +""" +Get set of mailbox files + +requires `besapi`, install with command `pip install besapi` +""" +import besapi + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + session_rel = 'tuple string items 0 of concatenations ", " of (it as string) of ids of bes computers whose(root server flag of it AND now - last report time of it < 30 * day)' + + # get root server computer id: + root_id = int(bes_conn.session_relevance_string(session_rel).strip()) + + print(root_id) + + # list mailbox files: + result = bes_conn.get(bes_conn.url(f"mailbox/{root_id}")) + + print(result) + + +if __name__ == "__main__": + main() From 9e07e1d32b64984ab780a6adda12ba7835e9ee08 Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 11 Aug 2023 13:06:25 -0400 Subject: [PATCH 065/231] example to create mailbox file --- examples/mailbox_files_create.py | 40 ++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 examples/mailbox_files_create.py diff --git a/examples/mailbox_files_create.py b/examples/mailbox_files_create.py new file mode 100644 index 0000000..da29f72 --- /dev/null +++ b/examples/mailbox_files_create.py @@ -0,0 +1,40 @@ +""" +Get set of mailbox files + +requires `besapi`, install with command `pip install besapi` +""" +import os + +import besapi + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + session_rel = 'tuple string items 0 of concatenations ", " of (it as string) of ids of bes computers whose(root server flag of it AND now - last report time of it < 30 * day)' + + # get root server computer id: + root_id = int(bes_conn.session_relevance_string(session_rel).strip()) + + print(root_id) + + file_path = "examples/mailbox_files_create.py" + file_name = os.path.basename(file_path) + + # https://developer.bigfix.com/rest-api/api/mailbox.html + + # Example Header:: Content-Disposition: attachment; filename="file.xml" + headers = {"Content-Disposition": f'attachment; filename="{file_name}"'} + with open(file_path, "rb") as f: + result = bes_conn.post( + bes_conn.url(f"mailbox/{root_id}"), data=f, headers=headers + ) + + print(result) + + +if __name__ == "__main__": + main() From 33aa26f82249300369afee133c50e968f73aa17d Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 23 Aug 2023 11:08:21 -0400 Subject: [PATCH 066/231] Update setup.py point the pip at the new location --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 31a5a44..cafd69b 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ description="Library for working with the BigFix REST API", license="MIT", keywords="bigfix iem tem rest api", - url="https://github.com/CLCMacTeam/besapi", + url="https://github.com/jgstew/besapi", # long_description= moved to setup.cfg packages=["besapi", "bescli"], package_data={"besapi": ["schemas/*.xsd"]}, From bb49902e7737d9dc9f051b4915af0f431a3e33da Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 23 Aug 2023 11:09:48 -0400 Subject: [PATCH 067/231] Update __init__.py update release and pip with new url --- src/besapi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/__init__.py b/src/besapi/__init__.py index c84f962..adedebe 100644 --- a/src/besapi/__init__.py +++ b/src/besapi/__init__.py @@ -6,4 +6,4 @@ from . import besapi -__version__ = "3.1.6" +__version__ = "3.1.7" From 6245b659d0625a8773b990e5a177b8ad58b7fc03 Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 11 Sep 2023 14:41:34 -0400 Subject: [PATCH 068/231] tweak examples --- ...py => session_relevance_from_file_json.py} | 13 +++++++ examples/session_relevance_from_string.py | 36 +++++++++++++++++++ 2 files changed, 49 insertions(+) rename examples/{session_relevance_json.py => session_relevance_from_file_json.py} (69%) create mode 100644 examples/session_relevance_from_string.py diff --git a/examples/session_relevance_json.py b/examples/session_relevance_from_file_json.py similarity index 69% rename from examples/session_relevance_json.py rename to examples/session_relevance_from_file_json.py index 14da804..8b91037 100644 --- a/examples/session_relevance_json.py +++ b/examples/session_relevance_from_file_json.py @@ -5,6 +5,8 @@ requires `besapi`, install with command `pip install besapi` """ +import json + import besapi @@ -23,6 +25,17 @@ def main(): print(result) + json_result = json.loads(str(result)) + + json_string = json.dumps(json_result, indent=2) + + print(json_string) + + with open( + "examples/session_relevance_query_from_file_output.json", "w" + ) as file_out: + file_out.write(json_string) + if __name__ == "__main__": main() diff --git a/examples/session_relevance_from_string.py b/examples/session_relevance_from_string.py new file mode 100644 index 0000000..1421e78 --- /dev/null +++ b/examples/session_relevance_from_string.py @@ -0,0 +1,36 @@ +""" +Example session relevance results from a file + +requires `besapi`, install with command `pip install besapi` +""" +import json + +import besapi + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + session_relevance = '(multiplicity of it, it) of unique values of (it as trimmed string) of (preceding text of first "|" of it | it) of values of results of bes properties "Installed Applications - Windows"' + + data = {"output": "json", "relevance": session_relevance} + + result = bes_conn.post(bes_conn.url("query"), data) + + json_result = json.loads(str(result)) + + json_string = json.dumps(json_result, indent=2) + + print(json_string) + + with open( + "examples/session_relevance_query_from_string_output.json", "w" + ) as file_out: + file_out.write(json_string) + + +if __name__ == "__main__": + main() From 109b894c2cedc827004639fcb0189c921f2d6e9e Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 11 Oct 2023 15:02:56 -0400 Subject: [PATCH 069/231] add upload files example, improve upload function --- examples/upload_files.py | 32 ++++++++++++++++++++++++++++++++ examples/upload_files/README.md | 1 + src/besapi/besapi.py | 7 +++++++ 3 files changed, 40 insertions(+) create mode 100644 examples/upload_files.py create mode 100644 examples/upload_files/README.md diff --git a/examples/upload_files.py b/examples/upload_files.py new file mode 100644 index 0000000..428d6de --- /dev/null +++ b/examples/upload_files.py @@ -0,0 +1,32 @@ +""" +Upload files in folder. + +requires `besapi`, install with command `pip install besapi` +""" + +import os + +import besapi + + +def main(path_folder="./tmp"): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + print(f"INFO: Uploading new files within: {os.path.abspath(path_folder)}") + + for entry in os.scandir(path_folder): + if entry.is_file() and "README.md" not in entry.path: + if " " in os.path.basename(entry.path): + print(f"ERROR: files cannot contain spaces! skipping: {entry.path}") + continue + print(f"Processing: {entry.path}") + output = bes_conn.upload(entry.path) + # print(output) + print(bes_conn.parse_upload_result_to_prefetch(output)) + + +if __name__ == "__main__": + main("./examples/upload_files") diff --git a/examples/upload_files/README.md b/examples/upload_files/README.md new file mode 100644 index 0000000..5ace3f2 --- /dev/null +++ b/examples/upload_files/README.md @@ -0,0 +1 @@ +put files in this folder to have them be uploaded to the root server by the script upload_files.py diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index e86feb9..4622a4c 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -591,6 +591,13 @@ def upload(self, file_path, file_name=None, file_hash=None): if not file_name: file_name = os.path.basename(file_path) + # files cannot contain spaces: + if " " in file_name: + besapi_logger.warning( + "Replacing spaces with underscores in `%s`", file_name + ) + file_name = file_name.replace(" ", "_") + if not file_hash: besapi_logger.warning( "SHA1 hash of file to be uploaded not provided, calculating it." From c9574b3629c513bc4a76ce96c4d55386331ae154 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 11 Oct 2023 15:06:54 -0400 Subject: [PATCH 070/231] trigger new release --- src/besapi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/__init__.py b/src/besapi/__init__.py index adedebe..a72dd99 100644 --- a/src/besapi/__init__.py +++ b/src/besapi/__init__.py @@ -6,4 +6,4 @@ from . import besapi -__version__ = "3.1.7" +__version__ = "3.1.8" From 708a0bf3e82ba2ffbc69e76b7a5fa2d0758ceeb8 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 11 Oct 2023 15:36:25 -0400 Subject: [PATCH 071/231] remove exception check for check_upload --- src/besapi/besapi.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 4622a4c..51ca01e 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -614,10 +614,7 @@ def upload(self, file_path, file_name=None, file_hash=None): check_upload = None if file_hash: - try: - check_upload = self.get_upload(str(file_name), str(file_hash)) - except BaseException as err: # pylint: disable=broad-except - print(err) + check_upload = self.get_upload(str(file_name), str(file_hash)) if check_upload: besapi_logger.warning( From 500d88e50940daa642663cdd96185debc0ef042d Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 11 Oct 2023 15:43:19 -0400 Subject: [PATCH 072/231] release improved upload function --- src/besapi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/__init__.py b/src/besapi/__init__.py index a72dd99..fc00cb2 100644 --- a/src/besapi/__init__.py +++ b/src/besapi/__init__.py @@ -6,4 +6,4 @@ from . import besapi -__version__ = "3.1.8" +__version__ = "3.1.9" From 8b791df79e16519d94880cb4bd1bb9284114dc8c Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 11 Oct 2023 15:53:21 -0400 Subject: [PATCH 073/231] add back check for spaces. --- examples/upload_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/upload_files.py b/examples/upload_files.py index 428d6de..ea8efd6 100644 --- a/examples/upload_files.py +++ b/examples/upload_files.py @@ -19,6 +19,7 @@ def main(path_folder="./tmp"): for entry in os.scandir(path_folder): if entry.is_file() and "README.md" not in entry.path: + # this check for spaces is not required for besapi>=3.1.9 if " " in os.path.basename(entry.path): print(f"ERROR: files cannot contain spaces! skipping: {entry.path}") continue From 15266a41f443707787677ae39cc59309426bce0f Mon Sep 17 00:00:00 2001 From: JGStew Date: Sat, 14 Oct 2023 15:21:53 -0400 Subject: [PATCH 074/231] add working example to delete computers by file --- examples/computers_delete_by_file.py | 52 +++++++++++++++++++++++++++ examples/computers_delete_by_file.txt | 2 ++ 2 files changed, 54 insertions(+) create mode 100644 examples/computers_delete_by_file.py create mode 100644 examples/computers_delete_by_file.txt diff --git a/examples/computers_delete_by_file.py b/examples/computers_delete_by_file.py new file mode 100644 index 0000000..dafe65c --- /dev/null +++ b/examples/computers_delete_by_file.py @@ -0,0 +1,52 @@ +""" +Delete computers in file + +requires `besapi`, install with command `pip install besapi` +""" +import os + +import besapi + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + script_dir = os.path.dirname(os.path.realpath(__file__)) + comp_file_path = os.path.join(script_dir, "computers_delete_by_file.txt") + + comp_file_lines = [] + with open(comp_file_path, "r") as comp_file: + for line in comp_file: + line = line.strip() + if line != "": + comp_file_lines.append(line) + + # print(comp_file_lines) + + computers = '"' + '";"'.join(comp_file_lines) + '"' + + session_relevance = f"ids of bes computers whose(now - last report time of it > 90 * day AND exists elements of intersections of (it; sets of ({computers})) of sets of (name of it; id of it as string))" + + results = bes_conn.session_relevance_array(session_relevance) + + # print(results) + + if "Nothing returned, but no error." in results[0]: + print("WARNING: No computers found to delete!") + return None + + for item in results: + if item.strip() != "": + computer_id = str(int(item)) + print(f"INFO: Attempting to delete Computer ID: {computer_id}") + result_del = bes_conn.delete(bes_conn.url(f"computer/{computer_id}")) + if "ok" not in result_del.text: + print(f"ERROR: {result_del} for id: {computer_id}") + continue + + +if __name__ == "__main__": + main() diff --git a/examples/computers_delete_by_file.txt b/examples/computers_delete_by_file.txt new file mode 100644 index 0000000..c59cc26 --- /dev/null +++ b/examples/computers_delete_by_file.txt @@ -0,0 +1,2 @@ +7cf29da417f9 +15083644 From 55bf631c555634b12d68356f346e961ae24f7164 Mon Sep 17 00:00:00 2001 From: JGStew Date: Sat, 14 Oct 2023 15:24:17 -0400 Subject: [PATCH 075/231] improve session relevance --- examples/computers_delete_by_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/computers_delete_by_file.py b/examples/computers_delete_by_file.py index dafe65c..8d4ef3b 100644 --- a/examples/computers_delete_by_file.py +++ b/examples/computers_delete_by_file.py @@ -28,7 +28,7 @@ def main(): computers = '"' + '";"'.join(comp_file_lines) + '"' - session_relevance = f"ids of bes computers whose(now - last report time of it > 90 * day AND exists elements of intersections of (it; sets of ({computers})) of sets of (name of it; id of it as string))" + session_relevance = f"unique values of ids of bes computers whose(now - last report time of it > 90 * day AND exists elements of intersections of (it; sets of ({computers})) of sets of (name of it; id of it as string))" results = bes_conn.session_relevance_array(session_relevance) From b0ccf7715364325343998ff3210d130e71327720 Mon Sep 17 00:00:00 2001 From: JGStew Date: Sat, 14 Oct 2023 15:28:27 -0400 Subject: [PATCH 076/231] add comments --- examples/computers_delete_by_file.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/computers_delete_by_file.py b/examples/computers_delete_by_file.py index 8d4ef3b..1b358a1 100644 --- a/examples/computers_delete_by_file.py +++ b/examples/computers_delete_by_file.py @@ -14,7 +14,9 @@ def main(): bes_conn = besapi.besapi.get_bes_conn_using_config_file() bes_conn.login() + # get the directory this script is running within: script_dir = os.path.dirname(os.path.realpath(__file__)) + # get the file "computers_delete_by_file.txt" within the folder of the script: comp_file_path = os.path.join(script_dir, "computers_delete_by_file.txt") comp_file_lines = [] @@ -28,8 +30,10 @@ def main(): computers = '"' + '";"'.join(comp_file_lines) + '"' + # by default, this will only return computers that have not reported in >90 days: session_relevance = f"unique values of ids of bes computers whose(now - last report time of it > 90 * day AND exists elements of intersections of (it; sets of ({computers})) of sets of (name of it; id of it as string))" + # get session relevance result of computer ids from list of computer ids or computer names: results = bes_conn.session_relevance_array(session_relevance) # print(results) @@ -38,6 +42,7 @@ def main(): print("WARNING: No computers found to delete!") return None + # delete computers: for item in results: if item.strip() != "": computer_id = str(int(item)) From 800dfb7c03f7d2fd155078dc3863b32960e14e0d Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 21 Feb 2024 13:17:33 -0500 Subject: [PATCH 077/231] add example to use restapi using cmd args --- examples/rest_cmd_args.py | 58 +++++++++++++++++++++++ examples/session_relevance_from_string.py | 3 +- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 examples/rest_cmd_args.py diff --git a/examples/rest_cmd_args.py b/examples/rest_cmd_args.py new file mode 100644 index 0000000..ec73626 --- /dev/null +++ b/examples/rest_cmd_args.py @@ -0,0 +1,58 @@ +""" +Example session relevance results from a string + +requires `besapi`, install with command `pip install besapi` + +Example Usage: +python rest_cmd_args.py -r https://localhost:52311/api -u API_USER -p API_PASSWORD +""" + +import argparse +import json + +import besapi + + +def main(): + """Execution starts here""" + print("main()") + + parser = argparse.ArgumentParser( + description="Provde command line arguments for REST URL, username, and password" + ) + parser.add_argument("-r", "--rest-url", help="Specify the REST URL", required=True) + parser.add_argument( + "-besserver", "--besserver", help="Specify the BES URL", required=False + ) + parser.add_argument("-u", "--user", help="Specify the username", required=True) + parser.add_argument("-p", "--password", help="Specify the password", required=True) + # allow unknown args to be parsed instead of throwing an error: + args, _unknown = parser.parse_known_args() + + rest_url = args.rest_url + + if rest_url.endswith("/api"): + rest_url = rest_url.replace("/api", "") + + try: + bes_conn = besapi.besapi.BESConnection(args.user, args.password, rest_url) + bes_conn.login() + except (ConnectionRefusedError, besapi.besapi.requests.exceptions.ConnectionError): + # print(args.besserver) + bes_conn = besapi.besapi.BESConnection(args.user, args.password, args.besserver) + + session_relevance = 'unique values of (it as trimmed string) of (preceding text of last " (" of it | it) of operating systems of bes computers' + + data = {"output": "json", "relevance": session_relevance} + + result = bes_conn.post(bes_conn.url("query"), data) + + json_result = json.loads(str(result)) + + json_string = json.dumps(json_result, indent=2) + + print(json_string) + + +if __name__ == "__main__": + main() diff --git a/examples/session_relevance_from_string.py b/examples/session_relevance_from_string.py index 1421e78..893326e 100644 --- a/examples/session_relevance_from_string.py +++ b/examples/session_relevance_from_string.py @@ -1,8 +1,9 @@ """ -Example session relevance results from a file +Example session relevance results from a string requires `besapi`, install with command `pip install besapi` """ + import json import besapi From ad740079c76e801c4e552ed3c256d4cf7f4115b5 Mon Sep 17 00:00:00 2001 From: jgstew Date: Wed, 21 Feb 2024 13:35:39 -0500 Subject: [PATCH 078/231] minor tweak, not needed. --- examples/rest_cmd_args.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/rest_cmd_args.py b/examples/rest_cmd_args.py index ec73626..95425e7 100644 --- a/examples/rest_cmd_args.py +++ b/examples/rest_cmd_args.py @@ -36,7 +36,7 @@ def main(): try: bes_conn = besapi.besapi.BESConnection(args.user, args.password, rest_url) - bes_conn.login() + # bes_conn.login() except (ConnectionRefusedError, besapi.besapi.requests.exceptions.ConnectionError): # print(args.besserver) bes_conn = besapi.besapi.BESConnection(args.user, args.password, args.besserver) From 6af60e278574830d0ce8ba8819017af6b8f977ad Mon Sep 17 00:00:00 2001 From: jgstew Date: Tue, 27 Feb 2024 11:58:18 -0500 Subject: [PATCH 079/231] add working client_query example --- examples/client_query_from_string.py | 74 ++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 examples/client_query_from_string.py diff --git a/examples/client_query_from_string.py b/examples/client_query_from_string.py new file mode 100644 index 0000000..4b003f8 --- /dev/null +++ b/examples/client_query_from_string.py @@ -0,0 +1,74 @@ +""" +Example session relevance results from a string + +requires `besapi`, install with command `pip install besapi` +""" + +import json +import time + +import besapi + + +def main(): + """Execution starts here""" + print("main()") + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + # get the ~10 most recent computers to report into BigFix: + session_relevance = 'tuple string items (integers in (0,9)) of concatenations ", " of (it as string) of ids of bes computers whose(now - last report time of it < 25 * minute)' + + data = {"output": "json", "relevance": session_relevance} + + result = bes_conn.post(bes_conn.url("query"), data) + + json_result = json.loads(str(result)) + + # json_string = json.dumps(json_result, indent=2) + # print(json_string) + + # print() + + for item in json_result["result"]: + print(item) + + client_relevance = "(computer names, operating systems)" + + target_xml = ( + "" + + "".join(json_result["result"]) + + "" + ) + + query_payload = f""" + + true + {client_relevance} + + {target_xml} + + +""" + + # print(query_payload) + + query_submit_result = bes_conn.post(bes_conn.url("clientquery"), data=query_payload) + + # print(query_submit_result) + # print(query_submit_result.besobj.ClientQuery.ID) + + print("... waiting for results ...") + + # TODO: loop this to keep getting more results until all return or any key pressed + time.sleep(20) + + query_result = bes_conn.get( + bes_conn.url(f"clientqueryresults/{query_submit_result.besobj.ClientQuery.ID}") + ) + + print(query_result) + + +if __name__ == "__main__": + main() From a1440a9f996f8d018775427416cdeba74841fd68 Mon Sep 17 00:00:00 2001 From: jgstew Date: Tue, 27 Feb 2024 11:58:29 -0500 Subject: [PATCH 080/231] tweak --- examples/session_relevance_from_file_json.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/session_relevance_from_file_json.py b/examples/session_relevance_from_file_json.py index 8b91037..c0ddb78 100644 --- a/examples/session_relevance_from_file_json.py +++ b/examples/session_relevance_from_file_json.py @@ -5,6 +5,7 @@ requires `besapi`, install with command `pip install besapi` """ + import json import besapi @@ -23,13 +24,15 @@ def main(): result = bes_conn.post(bes_conn.url("query"), data) - print(result) + if __debug__: + print(result) json_result = json.loads(str(result)) json_string = json.dumps(json_result, indent=2) - print(json_string) + if __debug__: + print(json_string) with open( "examples/session_relevance_query_from_file_output.json", "w" From 853e7a0e7d4df40924a2919497bdfb93a8c05017 Mon Sep 17 00:00:00 2001 From: jgstew Date: Tue, 27 Feb 2024 12:01:50 -0500 Subject: [PATCH 081/231] add comment --- examples/client_query_from_string.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/client_query_from_string.py b/examples/client_query_from_string.py index 4b003f8..3ab15ee 100644 --- a/examples/client_query_from_string.py +++ b/examples/client_query_from_string.py @@ -33,6 +33,7 @@ def main(): for item in json_result["result"]: print(item) + # this is the client relevance we are going to get the results of: client_relevance = "(computer names, operating systems)" target_xml = ( From e46a964fc65e88d5bb1504610bd2cd74ee4393c0 Mon Sep 17 00:00:00 2001 From: jgstew Date: Tue, 27 Feb 2024 12:08:42 -0500 Subject: [PATCH 082/231] comment out print statement used for debug --- examples/client_query_from_string.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/client_query_from_string.py b/examples/client_query_from_string.py index 3ab15ee..1799698 100644 --- a/examples/client_query_from_string.py +++ b/examples/client_query_from_string.py @@ -28,10 +28,8 @@ def main(): # json_string = json.dumps(json_result, indent=2) # print(json_string) - # print() - - for item in json_result["result"]: - print(item) + # for item in json_result["result"]: + # print(item) # this is the client relevance we are going to get the results of: client_relevance = "(computer names, operating systems)" From 4c083273db0667ccf930e11da447e61b980d22ba Mon Sep 17 00:00:00 2001 From: jgstew Date: Tue, 27 Feb 2024 12:59:13 -0500 Subject: [PATCH 083/231] add comments --- examples/client_query_from_string.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/client_query_from_string.py b/examples/client_query_from_string.py index 1799698..8c7a8bc 100644 --- a/examples/client_query_from_string.py +++ b/examples/client_query_from_string.py @@ -21,6 +21,7 @@ def main(): data = {"output": "json", "relevance": session_relevance} + # submitting session relevance query using POST to reduce problems: result = bes_conn.post(bes_conn.url("query"), data) json_result = json.loads(str(result)) @@ -34,12 +35,14 @@ def main(): # this is the client relevance we are going to get the results of: client_relevance = "(computer names, operating systems)" + # generate target XML substring from list of computer ids: target_xml = ( "" + "".join(json_result["result"]) + "" ) + # python template for ClientQuery BESAPI XML: query_payload = f""" true @@ -52,6 +55,7 @@ def main(): # print(query_payload) + # send the client query: (need it's ID to get results) query_submit_result = bes_conn.post(bes_conn.url("clientquery"), data=query_payload) # print(query_submit_result) @@ -62,6 +66,9 @@ def main(): # TODO: loop this to keep getting more results until all return or any key pressed time.sleep(20) + # get the actual results: + # NOTE: this might not return anything if no clients have returned results + # this can be checked again and again for more results: query_result = bes_conn.get( bes_conn.url(f"clientqueryresults/{query_submit_result.besobj.ClientQuery.ID}") ) From 99c53f820c734a6365c3203c39e4d5a2226f4de2 Mon Sep 17 00:00:00 2001 From: jgstew Date: Tue, 27 Feb 2024 13:15:28 -0500 Subject: [PATCH 084/231] add loop --- examples/client_query_from_string.py | 41 +++++++++++++++++++--------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/examples/client_query_from_string.py b/examples/client_query_from_string.py index 8c7a8bc..c3b348a 100644 --- a/examples/client_query_from_string.py +++ b/examples/client_query_from_string.py @@ -61,19 +61,34 @@ def main(): # print(query_submit_result) # print(query_submit_result.besobj.ClientQuery.ID) - print("... waiting for results ...") - - # TODO: loop this to keep getting more results until all return or any key pressed - time.sleep(20) - - # get the actual results: - # NOTE: this might not return anything if no clients have returned results - # this can be checked again and again for more results: - query_result = bes_conn.get( - bes_conn.url(f"clientqueryresults/{query_submit_result.besobj.ClientQuery.ID}") - ) - - print(query_result) + previous_result = "" + i = 0 + try: + # loop ~90 second for results + while i < 9: + print("... waiting for results ... Ctrl+C to quit loop") + + # TODO: loop this to keep getting more results until all return or any key pressed + time.sleep(10) + + # get the actual results: + # NOTE: this might not return anything if no clients have returned results + # this can be checked again and again for more results: + query_result = bes_conn.get( + bes_conn.url( + f"clientqueryresults/{query_submit_result.besobj.ClientQuery.ID}" + ) + ) + + if previous_result != str(query_result): + print(query_result) + previous_result = str(query_result) + + i += 1 + except KeyboardInterrupt: + print("loop interuppted") + + print("script finished") if __name__ == "__main__": From cef1a9b1bfa08c18f7d6b6c872f395753263724d Mon Sep 17 00:00:00 2001 From: jgstew Date: Tue, 27 Feb 2024 13:22:59 -0500 Subject: [PATCH 085/231] do not loop if not interactively run --- examples/client_query_from_string.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/examples/client_query_from_string.py b/examples/client_query_from_string.py index c3b348a..5445160 100644 --- a/examples/client_query_from_string.py +++ b/examples/client_query_from_string.py @@ -5,6 +5,7 @@ """ import json +import sys import time import besapi @@ -85,6 +86,12 @@ def main(): previous_result = str(query_result) i += 1 + + # if not running interactively: + # https://stackoverflow.com/questions/2356399/tell-if-python-is-in-interactive-mode + if not sys.__stdin__.isatty(): + print("not interactive, stopping loop") + break except KeyboardInterrupt: print("loop interuppted") From 60d0ebe4779c0d1ddbd0d29b1c7dd7aa9af96d80 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 28 Feb 2024 11:31:29 -0500 Subject: [PATCH 086/231] add comments --- examples/rest_cmd_args.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/rest_cmd_args.py b/examples/rest_cmd_args.py index 95425e7..2df8cdf 100644 --- a/examples/rest_cmd_args.py +++ b/examples/rest_cmd_args.py @@ -20,10 +20,10 @@ def main(): parser = argparse.ArgumentParser( description="Provde command line arguments for REST URL, username, and password" ) - parser.add_argument("-r", "--rest-url", help="Specify the REST URL", required=True) parser.add_argument( "-besserver", "--besserver", help="Specify the BES URL", required=False ) + parser.add_argument("-r", "--rest-url", help="Specify the REST URL", required=True) parser.add_argument("-u", "--user", help="Specify the username", required=True) parser.add_argument("-p", "--password", help="Specify the password", required=True) # allow unknown args to be parsed instead of throwing an error: @@ -31,6 +31,7 @@ def main(): rest_url = args.rest_url + # normalize url to https://HostOrIP:52311 if rest_url.endswith("/api"): rest_url = rest_url.replace("/api", "") @@ -41,6 +42,7 @@ def main(): # print(args.besserver) bes_conn = besapi.besapi.BESConnection(args.user, args.password, args.besserver) + # get unique device OSes session_relevance = 'unique values of (it as trimmed string) of (preceding text of last " (" of it | it) of operating systems of bes computers' data = {"output": "json", "relevance": session_relevance} From 98260d3cb6ff590661c5438762d5e3313a6a952b Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 28 Feb 2024 18:10:56 -0500 Subject: [PATCH 087/231] Update besapi.py --- src/besapi/besapi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 51ca01e..eda70c1 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,6 @@ from lxml import etree, objectify from pkg_resources import resource_filename -logging.basicConfig(level=logging.WARNING) besapi_logger = logging.getLogger("besapi") From 23bc70cb2388c091436d7803e2c41a131a362b85 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 28 Feb 2024 18:16:32 -0500 Subject: [PATCH 088/231] Update __init__.py changed logging --- src/besapi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/__init__.py b/src/besapi/__init__.py index fc00cb2..a58adab 100644 --- a/src/besapi/__init__.py +++ b/src/besapi/__init__.py @@ -6,4 +6,4 @@ from . import besapi -__version__ = "3.1.9" +__version__ = "3.2.1" From 27b22f8bc569cb2719941057d228da7494c74689 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 29 Feb 2024 10:13:31 -0500 Subject: [PATCH 089/231] add logging initialization only if run directly --- src/besapi/__main__.py | 4 ++++ src/besapi/besapi.py | 1 + src/bescli/__main__.py | 4 ++++ src/bescli/bescli.py | 1 + 4 files changed, 10 insertions(+) diff --git a/src/besapi/__main__.py b/src/besapi/__main__.py index 6269c0c..f1aee0d 100644 --- a/src/besapi/__main__.py +++ b/src/besapi/__main__.py @@ -1,6 +1,10 @@ """ To run this module directly """ + +import logging + from bescli import bescli +logging.basicConfig() bescli.main() diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index eda70c1..e55b314 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -999,4 +999,5 @@ def main(): if __name__ == "__main__": + logging.basicConfig() main() diff --git a/src/bescli/__main__.py b/src/bescli/__main__.py index 789509e..ff6901a 100644 --- a/src/bescli/__main__.py +++ b/src/bescli/__main__.py @@ -1,6 +1,10 @@ """ To run this module directly """ + +import logging + from . import bescli +logging.basicConfig() bescli.main() diff --git a/src/bescli/bescli.py b/src/bescli/bescli.py index 9bc5dd3..398bbed 100644 --- a/src/bescli/bescli.py +++ b/src/bescli/bescli.py @@ -421,4 +421,5 @@ def main(): if __name__ == "__main__": + logging.basicConfig() main() From 8f6b9ee1d3c4e6541b78ba71f2ddb35f22f48e75 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 29 Feb 2024 10:30:15 -0500 Subject: [PATCH 090/231] add logging --- examples/rest_cmd_args.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/rest_cmd_args.py b/examples/rest_cmd_args.py index 2df8cdf..283d30f 100644 --- a/examples/rest_cmd_args.py +++ b/examples/rest_cmd_args.py @@ -9,6 +9,7 @@ import argparse import json +import logging import besapi @@ -57,4 +58,5 @@ def main(): if __name__ == "__main__": + logging.basicConfig(level=logging.WARNING) main() From 7447efd32be5eb649b19930c6c3eda14f3cb9b28 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 29 Feb 2024 10:36:31 -0500 Subject: [PATCH 091/231] update pre-commits --- .pre-commit-config.yaml | 6 +++--- examples/baseline_by_relevance.py | 1 + examples/computers_delete_by_file.py | 1 + examples/export_bes_by_relevance.py | 1 + examples/export_bes_by_relevance_async.py | 1 + examples/export_bes_by_relevance_threads.py | 1 + examples/fixlet_add_mime_field.py | 1 + examples/mailbox_files_create.py | 1 + examples/mailbox_files_list.py | 1 + examples/parameters_secure_sourced_fixlet_action.py | 1 + examples/parameters_sourced_fixlet_action.py | 1 + examples/session_relevance_from_file.py | 1 + examples/wake_on_lan.py | 1 + src/bescli/__init__.py | 1 + 14 files changed, 16 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2a63908..fcb1cdc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ # https://github.com/pre-commit/pre-commit-hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.5.0 hooks: - id: check-yaml - id: check-json @@ -27,7 +27,7 @@ repos: # - id: no-commit-to-branch # args: [--branch, main] - repo: https://github.com/adrienverge/yamllint.git - rev: v1.28.0 + rev: v1.35.1 hooks: - id: yamllint args: [-c=.yamllint.yaml] @@ -36,6 +36,6 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 24.2.0 hooks: - id: black diff --git a/examples/baseline_by_relevance.py b/examples/baseline_by_relevance.py index 21938d4..7ef1be5 100644 --- a/examples/baseline_by_relevance.py +++ b/examples/baseline_by_relevance.py @@ -3,6 +3,7 @@ requires `besapi`, install with command `pip install besapi` """ + import datetime import os diff --git a/examples/computers_delete_by_file.py b/examples/computers_delete_by_file.py index 1b358a1..8542fc2 100644 --- a/examples/computers_delete_by_file.py +++ b/examples/computers_delete_by_file.py @@ -3,6 +3,7 @@ requires `besapi`, install with command `pip install besapi` """ + import os import besapi diff --git a/examples/export_bes_by_relevance.py b/examples/export_bes_by_relevance.py index 3f2a78a..9d959c8 100644 --- a/examples/export_bes_by_relevance.py +++ b/examples/export_bes_by_relevance.py @@ -3,6 +3,7 @@ requires `besapi`, install with command `pip install besapi` """ + import time import besapi diff --git a/examples/export_bes_by_relevance_async.py b/examples/export_bes_by_relevance_async.py index 18dd83a..a2cd8d3 100644 --- a/examples/export_bes_by_relevance_async.py +++ b/examples/export_bes_by_relevance_async.py @@ -3,6 +3,7 @@ requires `besapi`, install with command `pip install besapi` """ + import asyncio import configparser import os diff --git a/examples/export_bes_by_relevance_threads.py b/examples/export_bes_by_relevance_threads.py index 7881cbb..20778bb 100644 --- a/examples/export_bes_by_relevance_threads.py +++ b/examples/export_bes_by_relevance_threads.py @@ -3,6 +3,7 @@ requires `besapi`, install with command `pip install besapi` """ + import concurrent.futures import time diff --git a/examples/fixlet_add_mime_field.py b/examples/fixlet_add_mime_field.py index 1952f22..cafe104 100644 --- a/examples/fixlet_add_mime_field.py +++ b/examples/fixlet_add_mime_field.py @@ -3,6 +3,7 @@ Need to url escape site name https://bigfix:52311/api/sites """ + import lxml.etree import besapi diff --git a/examples/mailbox_files_create.py b/examples/mailbox_files_create.py index da29f72..695b3a9 100644 --- a/examples/mailbox_files_create.py +++ b/examples/mailbox_files_create.py @@ -3,6 +3,7 @@ requires `besapi`, install with command `pip install besapi` """ + import os import besapi diff --git a/examples/mailbox_files_list.py b/examples/mailbox_files_list.py index ef94f40..e0b4f40 100644 --- a/examples/mailbox_files_list.py +++ b/examples/mailbox_files_list.py @@ -3,6 +3,7 @@ requires `besapi`, install with command `pip install besapi` """ + import besapi diff --git a/examples/parameters_secure_sourced_fixlet_action.py b/examples/parameters_secure_sourced_fixlet_action.py index afeec9d..9c9a05a 100644 --- a/examples/parameters_secure_sourced_fixlet_action.py +++ b/examples/parameters_secure_sourced_fixlet_action.py @@ -3,6 +3,7 @@ requires `besapi`, install with command `pip install besapi` """ + import besapi # reference: https://software.bigfix.com/download/bes/100/util/BES10.0.7.52.xsd diff --git a/examples/parameters_sourced_fixlet_action.py b/examples/parameters_sourced_fixlet_action.py index 602bdf4..c0f3552 100644 --- a/examples/parameters_sourced_fixlet_action.py +++ b/examples/parameters_sourced_fixlet_action.py @@ -3,6 +3,7 @@ requires `besapi`, install with command `pip install besapi` """ + import besapi # reference: https://software.bigfix.com/download/bes/100/util/BES10.0.7.52.xsd diff --git a/examples/session_relevance_from_file.py b/examples/session_relevance_from_file.py index 6063ffd..bad0e29 100644 --- a/examples/session_relevance_from_file.py +++ b/examples/session_relevance_from_file.py @@ -3,6 +3,7 @@ requires `besapi`, install with command `pip install besapi` """ + import besapi diff --git a/examples/wake_on_lan.py b/examples/wake_on_lan.py index 61f2498..2a98862 100644 --- a/examples/wake_on_lan.py +++ b/examples/wake_on_lan.py @@ -13,6 +13,7 @@ - https://localhost:52311/rd-proxy?RequestUrl=cgi-bin/bfenterprise/BESMirrorRequest.exe/-textreport - Gather Download Request: https://localhost:52311/rd-proxy?RequestUrl=bfmirror/downloads/_ACTION_ID_/_DOWNLOAD_ID_ """ + import besapi diff --git a/src/bescli/__init__.py b/src/bescli/__init__.py index b2069aa..c3e459b 100644 --- a/src/bescli/__init__.py +++ b/src/bescli/__init__.py @@ -1,6 +1,7 @@ """ bescli provides a command line interface to interact with besapi """ + # https://stackoverflow.com/questions/279237/import-a-module-from-a-relative-path/4397291 from . import bescli From 85dc6d88a1a290881398359107fd43cccebc2014 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 29 Feb 2024 10:46:11 -0500 Subject: [PATCH 092/231] change where the version info is stored. --- setup.cfg | 2 +- src/besapi/__init__.py | 2 -- src/besapi/besapi.py | 2 ++ src/bescli/bescli.py | 5 ++++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index 4a4fecf..b83a2e4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] # single source version in besapi.__init__.__version__ # can get version on command line with: `python setup.py --version` -version = attr: besapi.__version__ +version = attr: besapi.besapi.__version__ long_description = file: README.md long_description_content_type = text/markdown classifiers = diff --git a/src/besapi/__init__.py b/src/besapi/__init__.py index a58adab..920c7b5 100644 --- a/src/besapi/__init__.py +++ b/src/besapi/__init__.py @@ -5,5 +5,3 @@ # https://stackoverflow.com/questions/279237/import-a-module-from-a-relative-path/4397291 from . import besapi - -__version__ = "3.2.1" diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index e55b314..c488c62 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,6 +29,8 @@ from lxml import etree, objectify from pkg_resources import resource_filename +__version__ = "3.2.2" + besapi_logger = logging.getLogger("besapi") diff --git a/src/bescli/bescli.py b/src/bescli/bescli.py index 398bbed..115b224 100644 --- a/src/bescli/bescli.py +++ b/src/bescli/bescli.py @@ -28,7 +28,10 @@ # this is for the case in which we are calling bescli from besapi import besapi -from besapi import __version__ +try: + from besapi.besapi import __version__ +except ImportError: + from besapi import __version__ class BESCLInterface(Cmd): From 179972925e3a73bed08d66522302919dcc392d8a Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 29 Feb 2024 10:56:46 -0500 Subject: [PATCH 093/231] fix tests --- .github/workflows/tag_and_release.yaml | 1 + tests/tests.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tag_and_release.yaml b/.github/workflows/tag_and_release.yaml index 12b71ed..a7370be 100644 --- a/.github/workflows/tag_and_release.yaml +++ b/.github/workflows/tag_and_release.yaml @@ -7,6 +7,7 @@ on: - master paths: - "src/besapi/__init__.py" + - "src/besapi/besapi.py" # - ".github/workflows/tag_and_release.yaml" jobs: diff --git a/tests/tests.py b/tests/tests.py index a5e3def..17d7e04 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -25,7 +25,7 @@ import besapi -print("besapi version: " + str(besapi.__version__)) +print("besapi version: " + str(besapi.besapi.__version__)) assert 15 == len(besapi.besapi.rand_password(15)) @@ -124,7 +124,8 @@ class RequestResult(object): upload_result = bigfix_cli.bes_conn.upload( "./besapi/__init__.py", "test_besapi_upload.txt" ) - print(upload_result) + # print(upload_result) + assert "test_besapi_upload.txt" in str(upload_result) print(bigfix_cli.bes_conn.parse_upload_result_to_prefetch(upload_result)) if os.name == "nt": @@ -134,3 +135,5 @@ class RequestResult(object): ) bes_conn = besapi.besapi.get_bes_conn_using_config_file() print("login succeeded:", bes_conn.login()) + +sys.exit(0) From 13dfc7430caf09d611c75ebd2a0cc129ca84bf82 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 7 Mar 2024 17:35:54 -0500 Subject: [PATCH 094/231] Update besapi.py fix issue with tuples --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index c488c62..72fedaa 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -352,7 +352,7 @@ def session_relevance_array(self, relevance, **kwargs): def session_relevance_string(self, relevance, **kwargs): """Get Session Relevance Results string""" - rel_result_array = self.session_relevance_array(relevance, **kwargs) + rel_result_array = self.session_relevance_array("(it as string) of " + relevance, **kwargs) return "\n".join(rel_result_array) def login(self): From 68b875fff590f0ccfd182b22f454ac0903b69b83 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 7 Mar 2024 17:38:13 -0500 Subject: [PATCH 095/231] Update besapi.py blackened --- src/besapi/besapi.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 72fedaa..749b7d5 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -352,7 +352,9 @@ def session_relevance_array(self, relevance, **kwargs): def session_relevance_string(self, relevance, **kwargs): """Get Session Relevance Results string""" - rel_result_array = self.session_relevance_array("(it as string) of " + relevance, **kwargs) + rel_result_array = self.session_relevance_array( + "(it as string) of " + relevance, **kwargs + ) return "\n".join(rel_result_array) def login(self): From 9afa1e1d3468ab2a379870d15cd16e748acb4c0b Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 12 Mar 2024 10:51:10 -0400 Subject: [PATCH 096/231] Update baseline_by_relevance.py add relevance filter --- examples/baseline_by_relevance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/baseline_by_relevance.py b/examples/baseline_by_relevance.py index 7ef1be5..fcd5c4d 100644 --- a/examples/baseline_by_relevance.py +++ b/examples/baseline_by_relevance.py @@ -22,7 +22,7 @@ def main(): fixlets_rel = 'fixlets whose(name of it starts with "Update:") of bes sites whose( external site flag of it AND name of it = "Updates for Windows Applications Extended" )' # this gets the info needed from the items to make the baseline: - session_relevance = f"""(it as string) of (url of site of it, ids of it, content id of default action of it | "Action1") of it whose(exists default action of it AND globally visible flag of it AND name of it does not contain "(Superseded)" AND exists applicable computers of it) of {fixlets_rel}""" + session_relevance = f"""(it as string) of (url of site of it, ids of it, content id of default action of it | "Action1") of it whose(exists default action of it AND globally visible flag of it AND name of it does not contain "(Superseded)" AND exists applicable computers whose(now - last report time of it < 60 * day) of it) of {fixlets_rel}""" print("getting items to add to baseline...") result = bes_conn.session_relevance_array(session_relevance) From a2447ca80d92b084f6bfaa030c3761062f7d0e3c Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 14 Mar 2024 15:07:50 -0400 Subject: [PATCH 097/231] add example baseline plugin --- examples/baseline_plugin.config.yaml | 9 + examples/baseline_plugin.py | 285 +++++++++++++++++++++++++++ 2 files changed, 294 insertions(+) create mode 100644 examples/baseline_plugin.config.yaml create mode 100644 examples/baseline_plugin.py diff --git a/examples/baseline_plugin.config.yaml b/examples/baseline_plugin.config.yaml new file mode 100644 index 0000000..ea6f5dd --- /dev/null +++ b/examples/baseline_plugin.config.yaml @@ -0,0 +1,9 @@ +--- +bigfix: + content: + Baselines: + automation: + trigger_file_path: baseline_plugin_run_now + sites: + - name: Updates for Windows Applications Extended + - name: Updates for Windows Applications diff --git a/examples/baseline_plugin.py b/examples/baseline_plugin.py new file mode 100644 index 0000000..8a68904 --- /dev/null +++ b/examples/baseline_plugin.py @@ -0,0 +1,285 @@ +""" +Generate patching baselines from sites + +requires `besapi`, install with command `pip install besapi` + +Example Usage: +python baseline_plugin.py -r https://localhost:52311/api -u API_USER -p API_PASSWORD + +References: +- https://github.com/jgstew/besapi/blob/master/examples/rest_cmd_args.py +- https://github.com/jgstew/besapi/blob/master/examples/baseline_by_relevance.py +- https://github.com/jgstew/tools/blob/master/Python/locate_self.py +""" + +import argparse +import datetime +import logging +import logging.handlers +import os +import platform +import sys + +import ruamel.yaml + +import besapi + +__version__ = "0.0.1" +verbose = 0 +bes_conn = None +invoke_folder = None +config_yaml = None + + +def get_invoke_folder(): + """Get the folder the script was invoked from + + References: + - https://github.com/jgstew/tools/blob/master/Python/locate_self.py + """ + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_folder = os.path.abspath(os.path.dirname(sys.executable)) + else: + if verbose: + print("running in a normal Python process") + invoke_folder = os.path.abspath(os.path.dirname(__file__)) + + if verbose: + print(f"invoke_folder = {invoke_folder}") + + return invoke_folder + + +def get_config(path="baseline_plugin.config.yaml"): + """load config from yaml file""" + + if not (os.path.isfile(path) and os.access(path, os.R_OK)): + path = os.path.join(invoke_folder, path) + + logging.info("loading config from: `%s`", path) + + if not (os.path.isfile(path) and os.access(path, os.R_OK)): + raise FileNotFoundError(path) + + with open(path, "r", encoding="utf-8") as stream: + yaml = ruamel.yaml.YAML(typ="safe", pure=True) + config_yaml = yaml.load(stream) + + if verbose > 1: + logging.debug(config_yaml["bigfix"]) + + return config_yaml + + +def test_file_exists(path): + """return true if file exists""" + + if not (os.path.isfile(path) and os.access(path, os.R_OK)): + path = os.path.join(invoke_folder, path) + + logging.info("testing if exists: `%s`", path) + + if os.path.isfile(path) and os.access(path, os.R_OK) and os.access(path, os.W_OK): + return path + + return False + + +def create_baseline_from_site(site): + """create a patching baseline from a site name + + References: + - https://github.com/jgstew/besapi/blob/master/examples/baseline_by_relevance.py""" + # fixlets of bes sites whose(exists (it as trimmed string as lowercase) whose(it = "Updates for Windows Applications Extended" as trimmed string as lowercase) of (display names of it; names of it)) + + site_name = site["name"] + logging.info("Create patching baseline for site: %s", site_name) + + fixlets_rel = ( + 'fixlets of bes sites whose(exists (it as trimmed string as lowercase) whose(it = "' + + site_name + + '" as trimmed string as lowercase) of (display names of it; names of it))' + ) + + session_relevance = f"""(it as string) of (url of site of it, ids of it, content id of default action of it | "Action1") of it whose(exists default action of it AND globally visible flag of it AND name of it does not contain "(Superseded)" AND exists applicable computers whose(now - last report time of it < 60 * day) of it) of {fixlets_rel}""" + + result = bes_conn.session_relevance_array(session_relevance) + + num_items = len(result) + + if num_items > 1: + logging.info("Number of items to add to baseline: %s", num_items) + + baseline_components = "" + + IncludeInRelevance = "true" + + fixlet_ids_str = "0" + + if num_items > 100: + IncludeInRelevance = "false" + + for item in result: + tuple_items = item.split(", ") + fixlet_ids_str += " ; " + tuple_items[1] + baseline_components += f""" + """ + + logging.debug(baseline_components) + + # baseline_rel = f"""exists relevant fixlets whose(id of it is contained by set of (296035803 ; 503585307)) of sites whose("Fixlet Site" = type of it AND "Enterprise Security" = name of it)""" + + # generate XML for baseline with template: + baseline_xml = f""" + + + Patches from {site_name} - {datetime.datetime.today().strftime('%Y-%m-%d')} + + exists relevant fixlets whose(id of it is contained by set of ({ fixlet_ids_str })) of sites whose("Fixlet Site" = type of it AND "{ site_name }" = name of it) + + {baseline_components} + + + + """ + + logging.debug("Baseline XML:\n%s", baseline_xml) + + file_path = "tmp_baseline.bes" + site_name = "Demo" + site_path = f"custom/{site_name}" + + # Does not work through console import: + with open(file_path, "w", encoding="utf-8") as f: + f.write(baseline_xml) + + logging.info("Importing generated baseline...") + import_result = bes_conn.import_bes_to_site(file_path, site_path) + + logging.info("Result: Import XML:\n%s", import_result) + + os.remove(file_path) + + +def process_baselines(config): + """generate baselines""" + + for site in config: + create_baseline_from_site(site) + + +def main(): + """Execution starts here""" + print("main()") + + parser = argparse.ArgumentParser( + description="Provde command line arguments for REST URL, username, and password" + ) + parser.add_argument( + "-v", + "--verbose", + help="Set verbose output", + required=False, + action="count", + default=0, + ) + parser.add_argument( + "-besserver", "--besserver", help="Specify the BES URL", required=False + ) + parser.add_argument("-r", "--rest-url", help="Specify the REST URL", required=False) + parser.add_argument("-u", "--user", help="Specify the username", required=False) + parser.add_argument("-p", "--password", help="Specify the password", required=False) + # allow unknown args to be parsed instead of throwing an error: + args, _unknown = parser.parse_known_args() + + # allow set global scoped vars + global bes_conn, verbose, config_yaml, invoke_folder + verbose = args.verbose + + # get folder the script was invoked from: + invoke_folder = get_invoke_folder() + + # set different log levels: + log_level = logging.WARNING + if verbose: + log_level = logging.INFO + if verbose > 1: + log_level = logging.DEBUG + + # get path to put log file in: + log_filename = os.path.join(invoke_folder, "baseline_plugin.log") + + handlers = [ + logging.handlers.RotatingFileHandler( + log_filename, maxBytes=5 * 1024 * 1024, backupCount=1 + ) + ] + + # log output to console if arg provided: + if verbose: + handlers.append(logging.StreamHandler()) + + # setup logging: + logging.basicConfig( + encoding="utf-8", + level=log_level, + format="%(asctime)s %(levelname)s:%(message)s", + handlers=handlers, + ) + logging.info("----- Starting New Session ------") + logging.debug("invoke folder: %s", invoke_folder) + logging.debug("Python version: %s", platform.sys.version) + logging.debug("BESAPI Module version: %s", besapi.__version__) + logging.debug("this plugin's version: %s", __version__) + + # process args, setup connection: + rest_url = args.rest_url + + # normalize url to https://HostOrIP:52311 + if rest_url and rest_url.endswith("/api"): + rest_url = rest_url.replace("/api", "") + + try: + bes_conn = besapi.besapi.BESConnection(args.user, args.password, rest_url) + # bes_conn.login() + except ( + AttributeError, + ConnectionRefusedError, + besapi.besapi.requests.exceptions.ConnectionError, + ): + try: + # print(args.besserver) + bes_conn = besapi.besapi.BESConnection( + args.user, args.password, args.besserver + ) + # handle case where args.besserver is None + # AttributeError: 'NoneType' object has no attribute 'startswith' + except AttributeError: + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + + # get config: + config_yaml = get_config() + + trigger_path = config_yaml["bigfix"]["content"]["Baselines"]["automation"][ + "trigger_file_path" + ] + + if test_file_exists(trigger_path): + process_baselines( + config_yaml["bigfix"]["content"]["Baselines"]["automation"]["sites"] + ) + # TODO: delete trigger file + else: + logging.info( + "Trigger File `%s` Does Not Exists, skipping execution!", trigger_path + ) + + logging.info("----- Ending Session ------") + + +if __name__ == "__main__": + main() From 6d39a30f1510395e8d35341b1d826b01f1cf3519 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 14 Mar 2024 15:17:23 -0400 Subject: [PATCH 098/231] relevance tweaks --- examples/baseline_plugin.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/examples/baseline_plugin.py b/examples/baseline_plugin.py index 8a68904..c547d5b 100644 --- a/examples/baseline_plugin.py +++ b/examples/baseline_plugin.py @@ -94,16 +94,13 @@ def create_baseline_from_site(site): References: - https://github.com/jgstew/besapi/blob/master/examples/baseline_by_relevance.py""" - # fixlets of bes sites whose(exists (it as trimmed string as lowercase) whose(it = "Updates for Windows Applications Extended" as trimmed string as lowercase) of (display names of it; names of it)) site_name = site["name"] logging.info("Create patching baseline for site: %s", site_name) - fixlets_rel = ( - 'fixlets of bes sites whose(exists (it as trimmed string as lowercase) whose(it = "' - + site_name - + '" as trimmed string as lowercase) of (display names of it; names of it))' - ) + # Example: + # fixlets of bes sites whose(exists (it as trimmed string as lowercase) whose(it = "Updates for Windows Applications Extended" as trimmed string as lowercase) of (display names of it; names of it)) + fixlets_rel = f'fixlets of bes sites whose(exists (it as trimmed string as lowercase) whose(it = "{site_name}" as trimmed string as lowercase) of (display names of it; names of it))' session_relevance = f"""(it as string) of (url of site of it, ids of it, content id of default action of it | "Action1") of it whose(exists default action of it AND globally visible flag of it AND name of it does not contain "(Superseded)" AND exists applicable computers whose(now - last report time of it < 60 * day) of it) of {fixlets_rel}""" @@ -131,7 +128,11 @@ def create_baseline_from_site(site): logging.debug(baseline_components) - # baseline_rel = f"""exists relevant fixlets whose(id of it is contained by set of (296035803 ; 503585307)) of sites whose("Fixlet Site" = type of it AND "Enterprise Security" = name of it)""" + baseline_rel = "true" + + # create baseline relevance such that only relevant if 1+ fixlet is relevant + if num_items > 100: + baseline_rel = f"""exists relevant fixlets whose(id of it is contained by set of ({ fixlet_ids_str })) of sites whose("Fixlet Site" = type of it AND "{ site_name }" = name of it)""" # generate XML for baseline with template: baseline_xml = f""" @@ -139,7 +140,7 @@ def create_baseline_from_site(site): Patches from {site_name} - {datetime.datetime.today().strftime('%Y-%m-%d')} - exists relevant fixlets whose(id of it is contained by set of ({ fixlet_ids_str })) of sites whose("Fixlet Site" = type of it AND "{ site_name }" = name of it) + {baseline_rel} {baseline_components} @@ -272,7 +273,8 @@ def main(): process_baselines( config_yaml["bigfix"]["content"]["Baselines"]["automation"]["sites"] ) - # TODO: delete trigger file + # delete trigger file + os.remove(test_file_exists(trigger_path)) else: logging.info( "Trigger File `%s` Does Not Exists, skipping execution!", trigger_path From a83af3146996a461a79d50856e99aa74f62f8c09 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 14 Mar 2024 15:21:11 -0400 Subject: [PATCH 099/231] improve trigger path handling --- examples/baseline_plugin.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/baseline_plugin.py b/examples/baseline_plugin.py index c547d5b..44a8827 100644 --- a/examples/baseline_plugin.py +++ b/examples/baseline_plugin.py @@ -269,12 +269,15 @@ def main(): "trigger_file_path" ] - if test_file_exists(trigger_path): + # check if file exists, if so, return path, else return false: + trigger_path = test_file_exists(trigger_path) + + if trigger_path: process_baselines( config_yaml["bigfix"]["content"]["Baselines"]["automation"]["sites"] ) # delete trigger file - os.remove(test_file_exists(trigger_path)) + os.remove(trigger_path) else: logging.info( "Trigger File `%s` Does Not Exists, skipping execution!", trigger_path From db178733c6d633f92c244789c1e510a572df861f Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 14 Mar 2024 15:22:02 -0400 Subject: [PATCH 100/231] fix message --- examples/baseline_plugin.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/baseline_plugin.py b/examples/baseline_plugin.py index 44a8827..1e8216c 100644 --- a/examples/baseline_plugin.py +++ b/examples/baseline_plugin.py @@ -279,9 +279,7 @@ def main(): # delete trigger file os.remove(trigger_path) else: - logging.info( - "Trigger File `%s` Does Not Exists, skipping execution!", trigger_path - ) + logging.info("Trigger File Does Not Exists, skipping execution!") logging.info("----- Ending Session ------") From e2553c43aeff353d0575bcb1de0c374ddd259194 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 14 Mar 2024 15:26:06 -0400 Subject: [PATCH 101/231] remove attempt at unusual baseline relevance --- examples/baseline_plugin.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/baseline_plugin.py b/examples/baseline_plugin.py index 1e8216c..e39c13e 100644 --- a/examples/baseline_plugin.py +++ b/examples/baseline_plugin.py @@ -130,9 +130,10 @@ def create_baseline_from_site(site): baseline_rel = "true" - # create baseline relevance such that only relevant if 1+ fixlet is relevant - if num_items > 100: - baseline_rel = f"""exists relevant fixlets whose(id of it is contained by set of ({ fixlet_ids_str })) of sites whose("Fixlet Site" = type of it AND "{ site_name }" = name of it)""" + # # This does not appear to work as expected: + # # create baseline relevance such that only relevant if 1+ fixlet is relevant + # if num_items > 100: + # baseline_rel = f"""exists relevant fixlets whose(id of it is contained by set of ({ fixlet_ids_str })) of sites whose("Fixlet Site" = type of it AND "{ site_name }" = name of it)""" # generate XML for baseline with template: baseline_xml = f""" From 11f05975b6a5e9ace95d6773a33d7cd33f4af05e Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 14 Mar 2024 15:28:07 -0400 Subject: [PATCH 102/231] improve logging --- examples/baseline_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/baseline_plugin.py b/examples/baseline_plugin.py index e39c13e..6822a1d 100644 --- a/examples/baseline_plugin.py +++ b/examples/baseline_plugin.py @@ -159,7 +159,7 @@ def create_baseline_from_site(site): with open(file_path, "w", encoding="utf-8") as f: f.write(baseline_xml) - logging.info("Importing generated baseline...") + logging.info("Importing generated baseline for %s ...", site_name) import_result = bes_conn.import_bes_to_site(file_path, site_path) logging.info("Result: Import XML:\n%s", import_result) From 97b4527f62584f1f5e6ce93b91cad7e5db95c427 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 14 Mar 2024 16:19:02 -0400 Subject: [PATCH 103/231] add the site subscription relevance to baseline if over 100 components --- examples/baseline_plugin.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/baseline_plugin.py b/examples/baseline_plugin.py index 6822a1d..22ffa44 100644 --- a/examples/baseline_plugin.py +++ b/examples/baseline_plugin.py @@ -128,7 +128,14 @@ def create_baseline_from_site(site): logging.debug(baseline_components) - baseline_rel = "true" + # only have the baseline be relevant for 60 days after creation: + baseline_rel = f'exists absolute values whose(it < 60 * day) of (current date - "{ datetime.datetime.today().strftime("%d %b %Y") }" as date)' + + if num_items > 100: + site_rel_query = f"""unique value of site level relevances of bes sites whose(exists (it as trimmed string as lowercase) whose(it = "{site_name}" as trimmed string as lowercase) of (display names of it; names of it))""" + site_rel = bes_conn.session_relevance_string(site_rel_query) + + baseline_rel = f"""( {baseline_rel} ) AND ( {site_rel} )""" # # This does not appear to work as expected: # # create baseline relevance such that only relevant if 1+ fixlet is relevant @@ -141,7 +148,8 @@ def create_baseline_from_site(site): Patches from {site_name} - {datetime.datetime.today().strftime('%Y-%m-%d')} - {baseline_rel} + + PT12H {baseline_components} From 014cfaad7e80abe8da4a9b1103448332017a6bda Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 14 Mar 2024 16:24:22 -0400 Subject: [PATCH 104/231] fix overloaded site_name --- examples/baseline_plugin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/baseline_plugin.py b/examples/baseline_plugin.py index 22ffa44..e8b1387 100644 --- a/examples/baseline_plugin.py +++ b/examples/baseline_plugin.py @@ -160,8 +160,10 @@ def create_baseline_from_site(site): logging.debug("Baseline XML:\n%s", baseline_xml) file_path = "tmp_baseline.bes" - site_name = "Demo" - site_path = f"custom/{site_name}" + + # the custom site to import the baseline into: + import_site_name = "Demo" + site_path = f"custom/{import_site_name}" # Does not work through console import: with open(file_path, "w", encoding="utf-8") as f: From 66fa6df50e99dfeb70611fe7d664261a363cbc37 Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 29 Mar 2024 09:49:57 -0400 Subject: [PATCH 105/231] improve baseline plugin --- examples/baseline_plugin.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/baseline_plugin.py b/examples/baseline_plugin.py index e8b1387..9034083 100644 --- a/examples/baseline_plugin.py +++ b/examples/baseline_plugin.py @@ -186,7 +186,7 @@ def process_baselines(config): def main(): """Execution starts here""" - print("main()") + print("main() start") parser = argparse.ArgumentParser( description="Provde command line arguments for REST URL, username, and password" @@ -216,7 +216,7 @@ def main(): invoke_folder = get_invoke_folder() # set different log levels: - log_level = logging.WARNING + log_level = logging.INFO if verbose: log_level = logging.INFO if verbose > 1: @@ -225,6 +225,8 @@ def main(): # get path to put log file in: log_filename = os.path.join(invoke_folder, "baseline_plugin.log") + print(f"Log File Path: {log_filename}") + handlers = [ logging.handlers.RotatingFileHandler( log_filename, maxBytes=5 * 1024 * 1024, backupCount=1 @@ -245,7 +247,7 @@ def main(): logging.info("----- Starting New Session ------") logging.debug("invoke folder: %s", invoke_folder) logging.debug("Python version: %s", platform.sys.version) - logging.debug("BESAPI Module version: %s", besapi.__version__) + logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) logging.debug("this plugin's version: %s", __version__) # process args, setup connection: @@ -293,6 +295,7 @@ def main(): logging.info("Trigger File Does Not Exists, skipping execution!") logging.info("----- Ending Session ------") + print("main() End") if __name__ == "__main__": From edc0e295b6eafbb2159161ef3055ceaeaa9c82b3 Mon Sep 17 00:00:00 2001 From: JGStew Date: Sat, 30 Mar 2024 22:42:57 -0400 Subject: [PATCH 106/231] update requirements --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index e871578..7f328c0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ cmd2 lxml requests +setuptools From ee1234ea309fba7b3f36ecd73d5ff8257ad8d21b Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 1 Apr 2024 12:56:45 -0400 Subject: [PATCH 107/231] add dashboard var get/set --- src/besapi/besapi.py | 30 +++++++++++++++++++++++++++++- tests/tests.py | 15 +++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 749b7d5..a0aeb65 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.2.2" +__version__ = "3.2.3" besapi_logger = logging.getLogger("besapi") @@ -393,6 +393,34 @@ def logout(self): self.session.cookies.clear() self.session.close() + def set_dashboard_variable_value( + self, dashboard_name, var_name, var_value, private=False + ): + """set the variable value from a dashboard datastore""" + + dash_var_xml = f""" + + {dashboard_name} + {var_name} + {str(private).lower()} + {var_value} + + + """ + + return self.post( + f"dashboardvariable/{dashboard_name}/{var_name}", data=dash_var_xml + ) + + def get_dashboard_variable_value(self, dashboard_name, var_name): + """get the variable value from a dashboard datastore""" + + return str( + self.get( + f"dashboardvariable/{dashboard_name}/{var_name}" + ).besobj.DashboardData.Value + ) + def validate_site_path(self, site_path, check_site_exists=True, raise_error=False): """make sure site_path is valid""" diff --git a/tests/tests.py b/tests/tests.py index 17d7e04..45856f6 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -5,6 +5,7 @@ import argparse import os +import random import subprocess import sys @@ -128,6 +129,20 @@ class RequestResult(object): assert "test_besapi_upload.txt" in str(upload_result) print(bigfix_cli.bes_conn.parse_upload_result_to_prefetch(upload_result)) + dashboard_name = "_PyBESAPI_tests.py" + var_name = "TestVarName" + var_value = "TestVarValue " + str(random.randint(0, 9999)) + + print( + bigfix_cli.bes_conn.set_dashboard_variable_value( + dashboard_name, var_name, var_value + ) + ) + + assert var_value in str( + bigfix_cli.bes_conn.get_dashboard_variable_value(dashboard_name, var_name) + ) + if os.name == "nt": subprocess.run( 'CMD /C python -m besapi ls clear ls conf "query number of bes computers" version error_count exit', From c3f2f48db75abcd8935f79f853f7398241100d5d Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 1 Apr 2024 13:01:26 -0400 Subject: [PATCH 108/231] bump version again --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index a0aeb65..45fadfb 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.2.3" +__version__ = "3.2.4" besapi_logger = logging.getLogger("besapi") From 17156240f5dffdd253fa2303454760e783fa8f04 Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 1 Apr 2024 13:04:24 -0400 Subject: [PATCH 109/231] update git actions --- .github/workflows/black.yaml | 2 +- .github/workflows/flake8.yaml | 4 ++-- .github/workflows/isort.yaml | 4 ++-- .github/workflows/misspell.yaml | 4 ++-- .github/workflows/tag_and_release.yaml | 8 +++++--- .github/workflows/yamllint.yaml | 4 ++-- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index b8b38b6..5490de8 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -18,7 +18,7 @@ jobs: name: runner / black formatter runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Run black formatter checks # https://github.com/rickstaa/action-black uses: rickstaa/action-black@v1 diff --git a/.github/workflows/flake8.yaml b/.github/workflows/flake8.yaml index 8c12047..fe66f2b 100644 --- a/.github/workflows/flake8.yaml +++ b/.github/workflows/flake8.yaml @@ -20,8 +20,8 @@ jobs: name: Python Lint Flake8 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 with: python-version: "3.8" - name: Install flake8 diff --git a/.github/workflows/isort.yaml b/.github/workflows/isort.yaml index 3fb5ea7..6a3ef52 100644 --- a/.github/workflows/isort.yaml +++ b/.github/workflows/isort.yaml @@ -20,8 +20,8 @@ jobs: name: runner / isort runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v3 with: python-version: "3.8" - name: Install isort diff --git a/.github/workflows/misspell.yaml b/.github/workflows/misspell.yaml index c116120..d78842c 100644 --- a/.github/workflows/misspell.yaml +++ b/.github/workflows/misspell.yaml @@ -9,11 +9,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code. - uses: actions/checkout@v1 + uses: actions/checkout@v3 - name: misspell if: ${{ !env.ACT }} uses: reviewdog/action-misspell@v1 with: github_token: ${{ secrets.github_token }} locale: "US" - reporter: github-check # Change reporter. + reporter: github-check # Change reporter. diff --git a/.github/workflows/tag_and_release.yaml b/.github/workflows/tag_and_release.yaml index a7370be..c2c6bc0 100644 --- a/.github/workflows/tag_and_release.yaml +++ b/.github/workflows/tag_and_release.yaml @@ -22,6 +22,10 @@ jobs: uses: actions/setup-python@v3 with: python-version: 3.8 + + - name: Install requirements + run: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Read VERSION file id: getversion run: echo "::set-output name=version::$(python ./setup.py --version)" @@ -42,9 +46,7 @@ jobs: running-workflow-name: "Tag and Release" repo-token: ${{ secrets.GITHUB_TOKEN }} wait-interval: 30 - - name: Install requirements - if: steps.tagged.outputs.tagged == 1 - run: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Install build tools if: steps.tagged.outputs.tagged == 1 run: pip install setuptools wheel build diff --git a/.github/workflows/yamllint.yaml b/.github/workflows/yamllint.yaml index 28557d0..4b43a46 100644 --- a/.github/workflows/yamllint.yaml +++ b/.github/workflows/yamllint.yaml @@ -15,10 +15,10 @@ jobs: yamllint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.8 From 8f2a6a7842600ec7755a8b1231082771a19a269a Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 1 Apr 2024 13:11:55 -0400 Subject: [PATCH 110/231] update actions --- .github/workflows/tag_and_release.yaml | 3 ++- .github/workflows/test_build.yaml | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tag_and_release.yaml b/.github/workflows/tag_and_release.yaml index c2c6bc0..7987915 100644 --- a/.github/workflows/tag_and_release.yaml +++ b/.github/workflows/tag_and_release.yaml @@ -29,10 +29,11 @@ jobs: - name: Read VERSION file id: getversion run: echo "::set-output name=version::$(python ./setup.py --version)" + # only make release if there is NOT a git tag for this version - name: "Check: package version has corresponding git tag" # this will prevent this from doing anything when run through ACT - if: ${{ !env.ACT }} + if: ${{ !env.ACT AND contains(steps.getversion.outputs.version, '.') }} id: tagged shell: bash run: git show-ref --tags --verify --quiet -- "refs/tags/v${{ steps.getversion.outputs.version }}" && echo "::set-output name=tagged::0" || echo "::set-output name=tagged::1" diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index ac5ff74..1986a0c 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -41,6 +41,11 @@ jobs: run: pip install setuptools wheel build pyinstaller - name: Install requirements run: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Read VERSION file + id: getversion + run: echo "$(python ./setup.py --version)" + - name: Run Tests - Source run: python tests/tests.py - name: Run build From 685f0b34fa998ea0c28e5098872fd9f7445d9e2b Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 1 Apr 2024 13:15:40 -0400 Subject: [PATCH 111/231] update git actions --- .github/workflows/black.yaml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/flake8.yaml | 4 ++-- .github/workflows/isort.yaml | 4 ++-- .github/workflows/misspell.yaml | 2 +- .github/workflows/tag_and_release.yaml | 4 ++-- .github/workflows/test_build.yaml | 4 ++-- .github/workflows/yamllint.yaml | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/black.yaml b/.github/workflows/black.yaml index 5490de8..249cf58 100644 --- a/.github/workflows/black.yaml +++ b/.github/workflows/black.yaml @@ -18,7 +18,7 @@ jobs: name: runner / black formatter runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run black formatter checks # https://github.com/rickstaa/action-black uses: rickstaa/action-black@v1 diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index f91c030..d4c32ae 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -21,7 +21,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/flake8.yaml b/.github/workflows/flake8.yaml index fe66f2b..81838a3 100644 --- a/.github/workflows/flake8.yaml +++ b/.github/workflows/flake8.yaml @@ -20,8 +20,8 @@ jobs: name: Python Lint Flake8 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: "3.8" - name: Install flake8 diff --git a/.github/workflows/isort.yaml b/.github/workflows/isort.yaml index 6a3ef52..999bae1 100644 --- a/.github/workflows/isort.yaml +++ b/.github/workflows/isort.yaml @@ -20,8 +20,8 @@ jobs: name: runner / isort runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: "3.8" - name: Install isort diff --git a/.github/workflows/misspell.yaml b/.github/workflows/misspell.yaml index d78842c..5711ee4 100644 --- a/.github/workflows/misspell.yaml +++ b/.github/workflows/misspell.yaml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out code. - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: misspell if: ${{ !env.ACT }} uses: reviewdog/action-misspell@v1 diff --git a/.github/workflows/tag_and_release.yaml b/.github/workflows/tag_and_release.yaml index 7987915..2842471 100644 --- a/.github/workflows/tag_and_release.yaml +++ b/.github/workflows/tag_and_release.yaml @@ -17,9 +17,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: 3.8 diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index 1986a0c..e3042b0 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -32,9 +32,9 @@ jobs: # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json python-version: ["3.7", "3"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install build tools diff --git a/.github/workflows/yamllint.yaml b/.github/workflows/yamllint.yaml index 4b43a46..720e7e9 100644 --- a/.github/workflows/yamllint.yaml +++ b/.github/workflows/yamllint.yaml @@ -15,10 +15,10 @@ jobs: yamllint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: 3.8 From 5a9826847abc8d46ded2fb8922ce5071c383ce8f Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 1 Apr 2024 13:56:18 -0400 Subject: [PATCH 112/231] bump and release new version --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 45fadfb..43c1f8d 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.2.4" +__version__ = "3.2.5" besapi_logger = logging.getLogger("besapi") From 2fa71c8d3b328374557711523541a1e329854a81 Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 1 Apr 2024 13:58:42 -0400 Subject: [PATCH 113/231] fix if logic --- .github/workflows/tag_and_release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tag_and_release.yaml b/.github/workflows/tag_and_release.yaml index 2842471..311013d 100644 --- a/.github/workflows/tag_and_release.yaml +++ b/.github/workflows/tag_and_release.yaml @@ -33,7 +33,7 @@ jobs: # only make release if there is NOT a git tag for this version - name: "Check: package version has corresponding git tag" # this will prevent this from doing anything when run through ACT - if: ${{ !env.ACT AND contains(steps.getversion.outputs.version, '.') }} + if: ${{ !env.ACT }} && contains(steps.getversion.outputs.version, '.') id: tagged shell: bash run: git show-ref --tags --verify --quiet -- "refs/tags/v${{ steps.getversion.outputs.version }}" && echo "::set-output name=tagged::0" || echo "::set-output name=tagged::1" From 47cc21e131e2fa33f95e5caf90fa55f412d22b1a Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 1 Apr 2024 13:59:37 -0400 Subject: [PATCH 114/231] attempt to release a new version again --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 43c1f8d..dc70fe7 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.2.5" +__version__ = "3.2.6" besapi_logger = logging.getLogger("besapi") From f47d258bed2e220866564f427b143e5fcd4523c3 Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 1 Apr 2024 14:10:20 -0400 Subject: [PATCH 115/231] add recommended files --- .editorconfig | 21 +++++++++++++++++++++ .vscode/extensions.json | 8 ++++++++ 2 files changed, 29 insertions(+) create mode 100644 .editorconfig create mode 100644 .vscode/extensions.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7b96286 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# Check http://editorconfig.org for more information +# This is the main config file for this project: +root = true + +[*] +charset = utf-8 +trim_trailing_whitespace = true +indent_style = space +insert_final_newline = true + +[*.py] +indent_size = 4 + +[*.{bes,bes.mustache}] +# bes files are XML, but the `actionscript` tag text must use crlf +end_of_line = crlf +indent_style = tab +indent_size = 3 + +[*.{bat,cmd}] +end_of_line = crlf diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..e4611ec --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "EditorConfig.EditorConfig", + "github.vscode-github-actions", + "ms-python.black-formatter", + "ms-python.python" + ] +} From 5c0bda677d016160cadeffaec2fc67eeee65702e Mon Sep 17 00:00:00 2001 From: jgstew Date: Mon, 1 Apr 2024 14:14:57 -0400 Subject: [PATCH 116/231] add example that uses new functionality --- examples/dashboard_variable_get_value.py | 38 ++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 examples/dashboard_variable_get_value.py diff --git a/examples/dashboard_variable_get_value.py b/examples/dashboard_variable_get_value.py new file mode 100644 index 0000000..56effa2 --- /dev/null +++ b/examples/dashboard_variable_get_value.py @@ -0,0 +1,38 @@ +""" +get dashboard variable value + +requires `besapi` v3.2.6+ + +install with command `pip install -U besapi` +""" + +import besapi + + +def main(): + """Execution starts here""" + print("main()") + + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn.login() + + print(bes_conn.last_connected) + + print(bes_conn.get_dashboard_variable_value("WebUIAppAdmin", "Current_Sites")) + + # dashboard_name = "PyBESAPITest" + # var_name = "TestVar" + + # print( + # bes_conn.set_dashboard_variable_value( + # dashboard_name, var_name, "dashboard_variable_get_value.py 12345678" + # ) + # ) + + # print(bes_conn.get_dashboard_variable_value(dashboard_name, var_name)) + + # print(bes_conn.delete(f"dashboardvariable/{dashboard_name}/{var_name}")) + + +if __name__ == "__main__": + main() From ffa89012d6ee87bc885462279860c94e792f503c Mon Sep 17 00:00:00 2001 From: jgstew Date: Tue, 23 Apr 2024 16:45:37 -0400 Subject: [PATCH 117/231] fix bug introduced in previous change --- src/besapi/besapi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index dc70fe7..150fc98 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.2.6" +__version__ = "3.2.7" besapi_logger = logging.getLogger("besapi") @@ -353,7 +353,7 @@ def session_relevance_array(self, relevance, **kwargs): def session_relevance_string(self, relevance, **kwargs): """Get Session Relevance Results string""" rel_result_array = self.session_relevance_array( - "(it as string) of " + relevance, **kwargs + "(it as string) of ( " + relevance + " )", **kwargs ) return "\n".join(rel_result_array) From 28cb45021443a5a72b87bbc778c52907172fb398 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 9 May 2024 15:59:26 -0400 Subject: [PATCH 118/231] allow baseline plugin to prompt for password --- examples/baseline_plugin.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/baseline_plugin.py b/examples/baseline_plugin.py index 9034083..03d6058 100644 --- a/examples/baseline_plugin.py +++ b/examples/baseline_plugin.py @@ -14,6 +14,7 @@ import argparse import datetime +import getpass import logging import logging.handlers import os @@ -250,6 +251,13 @@ def main(): logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) logging.debug("this plugin's version: %s", __version__) + password = args.password + + if not password: + logging.warning("Password was not provided, provide REST API password.") + print("Password was not provided, provide REST API password.") + password = getpass.getpass() + # process args, setup connection: rest_url = args.rest_url @@ -258,7 +266,7 @@ def main(): rest_url = rest_url.replace("/api", "") try: - bes_conn = besapi.besapi.BESConnection(args.user, args.password, rest_url) + bes_conn = besapi.besapi.BESConnection(args.user, password, rest_url) # bes_conn.login() except ( AttributeError, @@ -267,9 +275,7 @@ def main(): ): try: # print(args.besserver) - bes_conn = besapi.besapi.BESConnection( - args.user, args.password, args.besserver - ) + bes_conn = besapi.besapi.BESConnection(args.user, password, args.besserver) # handle case where args.besserver is None # AttributeError: 'NoneType' object has no attribute 'startswith' except AttributeError: From 2fc220b66449e5fceb48caa43bce0000b3936847 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 9 May 2024 16:00:37 -0400 Subject: [PATCH 119/231] add serversettings restapi example --- examples/serversettings.cfg | 9 ++ examples/serversettings.py | 241 ++++++++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 examples/serversettings.cfg create mode 100644 examples/serversettings.py diff --git a/examples/serversettings.cfg b/examples/serversettings.cfg new file mode 100644 index 0000000..8f1e776 --- /dev/null +++ b/examples/serversettings.cfg @@ -0,0 +1,9 @@ + +passwordComplexityDescription=Passwords must contain 12 characters or more, both uppercase and lowercase letters, and at least 1 digit. +passwordComplexityRegex=(?=.*[[:lower:]])(?=.*[[:upper:]])(?=.*[[:digit:]]).{12,} +disableNmoManualGroups = 1 +includeSFIDsInBaselineActions= 1 +requireConfirmAction =1 +loginTimeoutSeconds=7200 +timeoutLockMinutes=345 +timeoutLogoutMinutes=360 diff --git a/examples/serversettings.py b/examples/serversettings.py new file mode 100644 index 0000000..016e745 --- /dev/null +++ b/examples/serversettings.py @@ -0,0 +1,241 @@ +""" +Set server settings like clientsettings.cfg + +requires `besapi`, install with command `pip install besapi` + +Example Usage: +python serversettings.py -r https://localhost:52311/api -u API_USER -p API_PASSWORD + +References: +- https://github.com/jgstew/besapi/blob/master/examples/rest_cmd_args.py +- https://github.com/jgstew/tools/blob/master/Python/locate_self.py +""" + +import argparse +import configparser +import getpass +import logging +import logging.handlers +import os +import platform +import sys + +import besapi + +__version__ = "0.0.1" +verbose = 0 +bes_conn = None +invoke_folder = None +config_ini = None + + +def get_invoke_folder(): + """Get the folder the script was invoked from + + References: + - https://github.com/jgstew/tools/blob/master/Python/locate_self.py + """ + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_folder = os.path.abspath(os.path.dirname(sys.executable)) + else: + if verbose: + print("running in a normal Python process") + invoke_folder = os.path.abspath(os.path.dirname(__file__)) + + if verbose: + print(f"invoke_folder = {invoke_folder}") + + return invoke_folder + + +def get_config(path="serversettings.cfg"): + """load config from ini file""" + + if not (os.path.isfile(path) and os.access(path, os.R_OK)): + path = os.path.join(invoke_folder, path) + + logging.info("loading config from: `%s`", path) + + if not (os.path.isfile(path) and os.access(path, os.R_OK)): + raise FileNotFoundError(path) + + configparser_instance = configparser.ConfigParser() + + try: + # try read config file with section headers: + with open(path) as stream: + configparser_instance.read_string(stream.read()) + except configparser.MissingSectionHeaderError: + # if section header missing, add a fake one: + with open(path) as stream: + configparser_instance.read_string( + "[bigfix_server_admin_fields]\n" + stream.read() + ) + + config_ini = list(configparser_instance.items("bigfix_server_admin_fields")) + + logging.debug(config_ini) + + return config_ini + + +def test_file_exists(path): + """return true if file exists""" + + if not (os.path.isfile(path) and os.access(path, os.R_OK)): + path = os.path.join(invoke_folder, path) + + logging.info("testing if exists: `%s`", path) + + if os.path.isfile(path) and os.access(path, os.R_OK) and os.access(path, os.W_OK): + return path + + return False + + +def get_settings_xml(config): + """turn config into settings xml""" + + settings_xml = "" + + for setting in config: + settings_xml += f"""\n +\t{setting[0]} +\t{setting[1]} +""" + + settings_xml = ( + """""" + + settings_xml + + "\n" + ) + + return settings_xml + + +def post_settings(settings_xml): + """post settings to server""" + + return bes_conn.post("admin/fields", settings_xml) + + +def main(): + """Execution starts here""" + print("main() start") + + parser = argparse.ArgumentParser( + description="Provde command line arguments for REST URL, username, and password" + ) + parser.add_argument( + "-v", + "--verbose", + help="Set verbose output", + required=False, + action="count", + default=0, + ) + parser.add_argument( + "-besserver", "--besserver", help="Specify the BES URL", required=False + ) + parser.add_argument("-r", "--rest-url", help="Specify the REST URL", required=False) + parser.add_argument("-u", "--user", help="Specify the username", required=False) + parser.add_argument("-p", "--password", help="Specify the password", required=False) + # allow unknown args to be parsed instead of throwing an error: + args, _unknown = parser.parse_known_args() + + # allow set global scoped vars + global bes_conn, verbose, config_ini, invoke_folder + verbose = args.verbose + + # get folder the script was invoked from: + invoke_folder = get_invoke_folder() + + # set different log levels: + log_level = logging.INFO + if verbose: + log_level = logging.INFO + if verbose > 1: + log_level = logging.DEBUG + + # get path to put log file in: + log_filename = os.path.join(invoke_folder, "serversettings.log") + + print(f"Log File Path: {log_filename}") + + handlers = [ + logging.handlers.RotatingFileHandler( + log_filename, maxBytes=5 * 1024 * 1024, backupCount=1 + ) + ] + + # log output to console if arg provided: + if verbose: + handlers.append(logging.StreamHandler()) + + # setup logging: + logging.basicConfig( + encoding="utf-8", + level=log_level, + format="%(asctime)s %(levelname)s:%(message)s", + handlers=handlers, + ) + logging.info("----- Starting New Session ------") + logging.debug("invoke folder: %s", invoke_folder) + logging.debug("Python version: %s", platform.sys.version) + logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) + logging.debug("this plugin's version: %s", __version__) + + password = args.password + + if not password: + logging.warning("Password was not provided, provide REST API password.") + print("Password was not provided, provide REST API password.") + password = getpass.getpass() + + # process args, setup connection: + rest_url = args.rest_url + + # normalize url to https://HostOrIP:52311 + if rest_url and rest_url.endswith("/api"): + rest_url = rest_url.replace("/api", "") + + try: + bes_conn = besapi.besapi.BESConnection(args.user, password, rest_url) + # bes_conn.login() + except ( + AttributeError, + ConnectionRefusedError, + besapi.besapi.requests.exceptions.ConnectionError, + ): + try: + # print(args.besserver) + bes_conn = besapi.besapi.BESConnection(args.user, password, args.besserver) + # handle case where args.besserver is None + # AttributeError: 'NoneType' object has no attribute 'startswith' + except AttributeError: + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + + # get config: + config_ini = get_config() + + logging.info("getting settings_xml from config info") + + # process settings + settings_xml = get_settings_xml(config_ini) + + logging.debug(settings_xml) + + rest_result = post_settings(settings_xml) + + logging.info(rest_result) + + logging.info("----- Ending Session ------") + print("main() End") + + +if __name__ == "__main__": + main() From cc7f8f72ff406622fb8405eea26b3d05967f2ae9 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 9 May 2024 16:03:52 -0400 Subject: [PATCH 120/231] add comments --- examples/serversettings.cfg | 4 +++- examples/serversettings.py | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/serversettings.cfg b/examples/serversettings.cfg index 8f1e776..f7729f8 100644 --- a/examples/serversettings.cfg +++ b/examples/serversettings.cfg @@ -1,4 +1,6 @@ - +# this file is modeled after the clientsettings.cfg but for bigfix server admin fields +# see the script that uses this file here: +# https://github.com/jgstew/besapi/blob/master/examples/serversettings.py passwordComplexityDescription=Passwords must contain 12 characters or more, both uppercase and lowercase letters, and at least 1 digit. passwordComplexityRegex=(?=.*[[:lower:]])(?=.*[[:upper:]])(?=.*[[:digit:]]).{12,} disableNmoManualGroups = 1 diff --git a/examples/serversettings.py b/examples/serversettings.py index 016e745..63d0b44 100644 --- a/examples/serversettings.py +++ b/examples/serversettings.py @@ -1,6 +1,9 @@ """ Set server settings like clientsettings.cfg +See example serversettings.cfg file here: +- https://github.com/jgstew/besapi/blob/master/examples/serversettings.cfg + requires `besapi`, install with command `pip install besapi` Example Usage: @@ -55,6 +58,8 @@ def get_invoke_folder(): def get_config(path="serversettings.cfg"): """load config from ini file""" + # example config: https://github.com/jgstew/besapi/blob/master/examples/serversettings.cfg + if not (os.path.isfile(path) and os.access(path, os.R_OK)): path = os.path.join(invoke_folder, path) From d0f4a863478d5c0bd0c8eaff5387ee2d33d6069c Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 9 May 2024 16:07:17 -0400 Subject: [PATCH 121/231] add comment --- examples/serversettings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/serversettings.py b/examples/serversettings.py index 63d0b44..b2cf16b 100644 --- a/examples/serversettings.py +++ b/examples/serversettings.py @@ -10,6 +10,7 @@ python serversettings.py -r https://localhost:52311/api -u API_USER -p API_PASSWORD References: +- https://developer.bigfix.com/rest-api/api/admin.html - https://github.com/jgstew/besapi/blob/master/examples/rest_cmd_args.py - https://github.com/jgstew/tools/blob/master/Python/locate_self.py """ From 99eb5af85434f5cd55afa149e1dd9fd51b307672 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 6 Jun 2024 11:54:00 -0400 Subject: [PATCH 122/231] add work in progress setup server plugin service --- examples/setup_server_plugin_service.py | 213 ++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 examples/setup_server_plugin_service.py diff --git a/examples/setup_server_plugin_service.py b/examples/setup_server_plugin_service.py new file mode 100644 index 0000000..cef7ece --- /dev/null +++ b/examples/setup_server_plugin_service.py @@ -0,0 +1,213 @@ +""" +Setup the root server server plugin service with creds provided + +requires `besapi`, install with command `pip install besapi` + +Example Usage: +python setup_server_plugin_service.py -r https://localhost:52311/api -u API_USER -p API_PASSWORD + +References: +- https://developer.bigfix.com/rest-api/api/admin.html +- https://github.com/jgstew/besapi/blob/master/examples/rest_cmd_args.py +- https://github.com/jgstew/tools/blob/master/Python/locate_self.py +""" + +import argparse +import configparser +import getpass +import logging +import logging.handlers +import os +import platform +import sys + +import besapi + +__version__ = "0.0.1" +verbose = 0 +bes_conn = None +invoke_folder = None +config_ini = None + + +def get_invoke_folder(): + """Get the folder the script was invoked from + + References: + - https://github.com/jgstew/tools/blob/master/Python/locate_self.py + """ + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_folder = os.path.abspath(os.path.dirname(sys.executable)) + else: + if verbose: + print("running in a normal Python process") + invoke_folder = os.path.abspath(os.path.dirname(__file__)) + + if verbose: + print(f"invoke_folder = {invoke_folder}") + + return invoke_folder + + +def test_file_exists(path): + """return true if file exists""" + + if not (os.path.isfile(path) and os.access(path, os.R_OK)): + path = os.path.join(invoke_folder, path) + + logging.info("testing if exists: `%s`", path) + + if os.path.isfile(path) and os.access(path, os.R_OK) and os.access(path, os.W_OK): + return path + + return False + + +def main(): + """Execution starts here""" + print("main() start") + + parser = argparse.ArgumentParser( + description="Provde command line arguments for REST URL, username, and password" + ) + parser.add_argument( + "-v", + "--verbose", + help="Set verbose output", + required=False, + action="count", + default=0, + ) + parser.add_argument( + "-besserver", "--besserver", help="Specify the BES URL", required=False + ) + parser.add_argument("-r", "--rest-url", help="Specify the REST URL", required=False) + parser.add_argument("-u", "--user", help="Specify the username", required=False) + parser.add_argument("-p", "--password", help="Specify the password", required=False) + # allow unknown args to be parsed instead of throwing an error: + args, _unknown = parser.parse_known_args() + + # allow set global scoped vars + global bes_conn, verbose, config_ini, invoke_folder + verbose = args.verbose + + # get folder the script was invoked from: + invoke_folder = get_invoke_folder() + + # set different log levels: + log_level = logging.INFO + if verbose: + log_level = logging.INFO + if verbose > 1: + log_level = logging.DEBUG + + # get path to put log file in: + log_filename = os.path.join(invoke_folder, "serversettings.log") + + print(f"Log File Path: {log_filename}") + + handlers = [ + logging.handlers.RotatingFileHandler( + log_filename, maxBytes=5 * 1024 * 1024, backupCount=1 + ) + ] + + # log output to console if arg provided: + if verbose: + handlers.append(logging.StreamHandler()) + + # setup logging: + logging.basicConfig( + encoding="utf-8", + level=log_level, + format="%(asctime)s %(levelname)s:%(message)s", + handlers=handlers, + ) + logging.info("----- Starting New Session ------") + logging.debug("invoke folder: %s", invoke_folder) + logging.debug("Python version: %s", platform.sys.version) + logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) + logging.debug("this plugin's version: %s", __version__) + + password = args.password + + if not password: + logging.warning("Password was not provided, provide REST API password.") + print("Password was not provided, provide REST API password.") + password = getpass.getpass() + + # process args, setup connection: + rest_url = args.rest_url + + # normalize url to https://HostOrIP:52311 + if rest_url and rest_url.endswith("/api"): + rest_url = rest_url.replace("/api", "") + + try: + bes_conn = besapi.besapi.BESConnection(args.user, password, rest_url) + # bes_conn.login() + except ( + AttributeError, + ConnectionRefusedError, + besapi.besapi.requests.exceptions.ConnectionError, + ): + try: + # print(args.besserver) + bes_conn = besapi.besapi.BESConnection(args.user, password, args.besserver) + # handle case where args.besserver is None + # AttributeError: 'NoneType' object has no attribute 'startswith' + except AttributeError: + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + + root_id = int( + bes_conn.session_relevance_string( + "unique value of ids of bes computers whose(root server flag of it)" + ) + ) + + # print(root_id) + + InstallPluginService_id = int( + bes_conn.session_relevance_string( + 'unique value of ids of fixlets whose(name of it contains "Install BES Server Plugin Service" AND exists applicable computers of it) of bes sites whose(name of it = "BES Support")' + ) + ) + + # print(InstallPluginService_id) + BES_SourcedFixletAction = f"""\ + + + + BES Support + {InstallPluginService_id} + Action1 + + + {root_id} + + + true + P10D + true + + + + """ + + if InstallPluginService_id > 0: + # create action to setup server plugin service: + action_result = bes_conn.post("actions", BES_SourcedFixletAction) + print(action_result) + + # NOTE: Work in progress + + logging.info("----- Ending Session ------") + print("main() End") + + +if __name__ == "__main__": + main() From 7ce048460b506c161548fac2f283423e953a56a6 Mon Sep 17 00:00:00 2001 From: jgstew Date: Thu, 6 Jun 2024 17:30:13 -0400 Subject: [PATCH 123/231] create MAG for server plugin service setup --- examples/setup_server_plugin_service.py | 82 +++++++++++++++++-------- 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/examples/setup_server_plugin_service.py b/examples/setup_server_plugin_service.py index cef7ece..c6a3a8a 100644 --- a/examples/setup_server_plugin_service.py +++ b/examples/setup_server_plugin_service.py @@ -106,7 +106,7 @@ def main(): log_level = logging.DEBUG # get path to put log file in: - log_filename = os.path.join(invoke_folder, "serversettings.log") + log_filename = os.path.join(invoke_folder, "setup_server_plugin_service.log") print(f"Log File Path: {log_filename}") @@ -173,37 +173,71 @@ def main(): InstallPluginService_id = int( bes_conn.session_relevance_string( - 'unique value of ids of fixlets whose(name of it contains "Install BES Server Plugin Service" AND exists applicable computers of it) of bes sites whose(name of it = "BES Support")' + 'unique value of ids of fixlets whose(name of it contains "Install BES Server Plugin Service") of bes sites whose(name of it = "BES Support")' ) ) - # print(InstallPluginService_id) - BES_SourcedFixletAction = f"""\ - - + logging.info( + "Install BES Server Plugin Service content id: %s", InstallPluginService_id + ) + + ConfigureCredentials_id = int( + bes_conn.session_relevance_string( + 'unique value of ids of fixlets whose(name of it contains "Configure REST API credentials for BES Server Plugin Service") of bes sites whose(name of it = "BES Support")' + ) + ) + + logging.info( + "Configure REST API credentials for BES Server Plugin Service content id: %s", + ConfigureCredentials_id, + ) + + # NOTE: Work in progress + XML_String_MultiActionGroup = f""" + + + Setup Server Plugin Service + exists main gather service + + install initscripts + + // start +wait dnf -y install initscripts +// End + + true + + BES Support {InstallPluginService_id} Action1 - - {root_id} - - - true - P10D - true - - - - """ - - if InstallPluginService_id > 0: - # create action to setup server plugin service: - action_result = bes_conn.post("actions", BES_SourcedFixletAction) - print(action_result) - - # NOTE: Work in progress + + + + BES Support + {ConfigureCredentials_id} + Action1 + + {args.user} + + + + + true + P7D + + + {root_id} + + +""" + + # create action to setup server plugin service: + action_result = bes_conn.post("actions", XML_String_MultiActionGroup) + + logging.info(action_result) logging.info("----- Ending Session ------") print("main() End") From e6e4218e0b58be925bb338080ce114fd76c2734d Mon Sep 17 00:00:00 2001 From: jgstew Date: Fri, 7 Jun 2024 11:20:39 -0400 Subject: [PATCH 124/231] add wake on lan medic --- examples/setup_server_plugin_service.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/examples/setup_server_plugin_service.py b/examples/setup_server_plugin_service.py index c6a3a8a..f711be6 100644 --- a/examples/setup_server_plugin_service.py +++ b/examples/setup_server_plugin_service.py @@ -92,7 +92,7 @@ def main(): args, _unknown = parser.parse_known_args() # allow set global scoped vars - global bes_conn, verbose, config_ini, invoke_folder + global bes_conn, verbose, invoke_folder verbose = args.verbose # get folder the script was invoked from: @@ -169,7 +169,7 @@ def main(): ) ) - # print(root_id) + logging.info("Root server computer id: %s", root_id) InstallPluginService_id = int( bes_conn.session_relevance_string( @@ -192,6 +192,17 @@ def main(): ConfigureCredentials_id, ) + EnableWakeOnLAN_id = int( + bes_conn.session_relevance_string( + 'unique value of ids of fixlets whose(name of it contains "Enable Wake-on-LAN Medic") of bes sites whose(name of it = "BES Support")' + ) + ) + + logging.info( + "Enable Wake-on-LAN Medic content id: %s", + EnableWakeOnLAN_id, + ) + # NOTE: Work in progress XML_String_MultiActionGroup = f""" @@ -224,6 +235,13 @@ def main(): + + + BES Support + {EnableWakeOnLAN_id} + Action1 + + true P7D From 8377e3ae6bcc56e9742f83e18614f8a7e99524b5 Mon Sep 17 00:00:00 2001 From: JGStew Date: Fri, 21 Jun 2024 08:39:16 -0400 Subject: [PATCH 125/231] update relevance --- examples/setup_server_plugin_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/setup_server_plugin_service.py b/examples/setup_server_plugin_service.py index f711be6..f231ff2 100644 --- a/examples/setup_server_plugin_service.py +++ b/examples/setup_server_plugin_service.py @@ -211,7 +211,7 @@ def main(): exists main gather service install initscripts - + // start wait dnf -y install initscripts // End From 5b97a694111fb781c8f41209f91fd9a1e144cd93 Mon Sep 17 00:00:00 2001 From: JGStew Date: Fri, 21 Jun 2024 08:39:48 -0400 Subject: [PATCH 126/231] removed unused import --- examples/setup_server_plugin_service.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/setup_server_plugin_service.py b/examples/setup_server_plugin_service.py index f231ff2..ac7a49a 100644 --- a/examples/setup_server_plugin_service.py +++ b/examples/setup_server_plugin_service.py @@ -13,7 +13,6 @@ """ import argparse -import configparser import getpass import logging import logging.handlers From f6552ea15d2d096d7aea659282aac786c84823ca Mon Sep 17 00:00:00 2001 From: JGStew Date: Fri, 21 Jun 2024 08:41:05 -0400 Subject: [PATCH 127/231] wrap actionscript in cdata --- examples/setup_server_plugin_service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/setup_server_plugin_service.py b/examples/setup_server_plugin_service.py index ac7a49a..4875290 100644 --- a/examples/setup_server_plugin_service.py +++ b/examples/setup_server_plugin_service.py @@ -211,9 +211,9 @@ def main(): install initscripts - // start + +// End]]> true From 1911b92ed7ebcee6b137af9353884a3daf736fec Mon Sep 17 00:00:00 2001 From: JGStew Date: Fri, 21 Jun 2024 08:42:40 -0400 Subject: [PATCH 128/231] change to always log to console --- examples/setup_server_plugin_service.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/setup_server_plugin_service.py b/examples/setup_server_plugin_service.py index 4875290..cd6f274 100644 --- a/examples/setup_server_plugin_service.py +++ b/examples/setup_server_plugin_service.py @@ -115,9 +115,8 @@ def main(): ) ] - # log output to console if arg provided: - if verbose: - handlers.append(logging.StreamHandler()) + # log output to console: + handlers.append(logging.StreamHandler()) # setup logging: logging.basicConfig( From 066b119ac292889109c7c09a7c0d613ce0151bd6 Mon Sep 17 00:00:00 2001 From: JGStew Date: Fri, 21 Jun 2024 08:43:03 -0400 Subject: [PATCH 129/231] remove unused global --- examples/setup_server_plugin_service.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/setup_server_plugin_service.py b/examples/setup_server_plugin_service.py index cd6f274..030bd61 100644 --- a/examples/setup_server_plugin_service.py +++ b/examples/setup_server_plugin_service.py @@ -26,7 +26,6 @@ verbose = 0 bes_conn = None invoke_folder = None -config_ini = None def get_invoke_folder(): From d932aa5823ec092f0b7edf91adc6cb679f753b63 Mon Sep 17 00:00:00 2001 From: JGStew Date: Fri, 21 Jun 2024 08:44:19 -0400 Subject: [PATCH 130/231] update pylint --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index d2ec702..78baa69 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,5 +1,5 @@ [MESSAGES CONTROL] -disable = C0330, C0326, C0103, c-extension-no-member, cyclic-import, no-self-use, unused-argument +disable = C0103, c-extension-no-member, cyclic-import, no-self-use, unused-argument [format] max-line-length = 88 From f3f2957fd6b05343ebc3feda8d0e927b58e44a64 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Jun 2024 09:42:46 -0400 Subject: [PATCH 131/231] improved export all sites --- examples/export_all_sites.py | 164 ++++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 4 deletions(-) diff --git a/examples/export_all_sites.py b/examples/export_all_sites.py index 25ff854..99e868a 100644 --- a/examples/export_all_sites.py +++ b/examples/export_all_sites.py @@ -2,20 +2,176 @@ This will export all bigfix sites to a folder called `export` This is equivalent of running `python -m besapi export_all_sites` + +requires `besapi`, install with command `pip install besapi` + +Example Usage: +python export_all_sites.py -r https://localhost:52311/api -u API_USER -p API_PASSWORD + +References: +- https://developer.bigfix.com/rest-api/api/admin.html +- https://github.com/jgstew/besapi/blob/master/examples/rest_cmd_args.py +- https://github.com/jgstew/tools/blob/master/Python/locate_self.py """ +import argparse +import getpass +import logging +import logging.handlers import os +import platform +import shutil +import sys import besapi +__version__ = "0.0.1" +verbose = 0 +bes_conn = None +invoke_folder = None + + +def get_invoke_folder(): + """Get the folder the script was invoked from + + References: + - https://github.com/jgstew/tools/blob/master/Python/locate_self.py + """ + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_folder = os.path.abspath(os.path.dirname(sys.executable)) + else: + if verbose: + print("running in a normal Python process") + invoke_folder = os.path.abspath(os.path.dirname(__file__)) + + if verbose: + print(f"invoke_folder = {invoke_folder}") + + return invoke_folder + + +def test_file_exists(path): + """return true if file exists""" + + if not (os.path.isfile(path) and os.access(path, os.R_OK)): + path = os.path.join(invoke_folder, path) + + logging.info("testing if exists: `%s`", path) + + if os.path.isfile(path) and os.access(path, os.R_OK) and os.access(path, os.W_OK): + return path + + return False + def main(): """Execution starts here""" - print("main()") - bes_conn = besapi.besapi.get_bes_conn_using_config_file() - bes_conn.login() + print("main() start") + + parser = argparse.ArgumentParser( + description="Provde command line arguments for REST URL, username, and password" + ) + parser.add_argument( + "-v", + "--verbose", + help="Set verbose output", + required=False, + action="count", + default=0, + ) + parser.add_argument( + "-besserver", "--besserver", help="Specify the BES URL", required=False + ) + parser.add_argument("-r", "--rest-url", help="Specify the REST URL", required=False) + parser.add_argument("-u", "--user", help="Specify the username", required=False) + parser.add_argument("-p", "--password", help="Specify the password", required=False) + parser.add_argument("-d", "--delete", help="delete previous export", required=False, action='store_true') + # allow unknown args to be parsed instead of throwing an error: + args, _unknown = parser.parse_known_args() + + # allow set global scoped vars + global bes_conn, verbose, invoke_folder + verbose = args.verbose + + # get folder the script was invoked from: + invoke_folder = get_invoke_folder() + + # set different log levels: + log_level = logging.INFO + if verbose: + log_level = logging.INFO + if verbose > 1: + log_level = logging.DEBUG + + # get path to put log file in: + log_filename = os.path.join(invoke_folder, "export_all_sites.log") + + print(f"Log File Path: {log_filename}") + + handlers = [ + logging.handlers.RotatingFileHandler( + log_filename, maxBytes=5 * 1024 * 1024, backupCount=1 + ) + ] + + # log output to console: + handlers.append(logging.StreamHandler()) + + # setup logging: + logging.basicConfig( + encoding="utf-8", + level=log_level, + format="%(asctime)s %(levelname)s:%(message)s", + handlers=handlers, + ) + logging.info("----- Starting New Session ------") + logging.debug("invoke folder: %s", invoke_folder) + logging.debug("Python version: %s", platform.sys.version) + logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) + logging.debug("this plugin's version: %s", __version__) + + password = args.password + + if not password: + logging.warning("Password was not provided, provide REST API password.") + print("Password was not provided, provide REST API password.") + password = getpass.getpass() + + # process args, setup connection: + rest_url = args.rest_url + + # normalize url to https://HostOrIP:52311 + if rest_url and rest_url.endswith("/api"): + rest_url = rest_url.replace("/api", "") + + try: + bes_conn = besapi.besapi.BESConnection(args.user, password, rest_url) + # bes_conn.login() + except ( + AttributeError, + ConnectionRefusedError, + besapi.besapi.requests.exceptions.ConnectionError, + ): + try: + # print(args.besserver) + bes_conn = besapi.besapi.BESConnection(args.user, password, args.besserver) + # handle case where args.besserver is None + # AttributeError: 'NoneType' object has no attribute 'startswith' + except AttributeError: + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + + # if --delete arg used, delete export folder: + if args.delete: + shutil.rmtree("export", ignore_errors=True) - os.mkdir("export") + try: + os.mkdir("export") + except FileExistsError: + logging.warning("Folder already exists!") os.chdir("export") From 52d59c7cb43d18c18182893ac90feb971eac7a09 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Jun 2024 10:18:48 -0400 Subject: [PATCH 132/231] fix export path for plugin --- examples/export_all_sites.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/examples/export_all_sites.py b/examples/export_all_sites.py index 99e868a..9e37b1c 100644 --- a/examples/export_all_sites.py +++ b/examples/export_all_sites.py @@ -89,7 +89,13 @@ def main(): parser.add_argument("-r", "--rest-url", help="Specify the REST URL", required=False) parser.add_argument("-u", "--user", help="Specify the username", required=False) parser.add_argument("-p", "--password", help="Specify the password", required=False) - parser.add_argument("-d", "--delete", help="delete previous export", required=False, action='store_true') + parser.add_argument( + "-d", + "--delete", + help="delete previous export", + required=False, + action="store_true", + ) # allow unknown args to be parsed instead of throwing an error: args, _unknown = parser.parse_known_args() @@ -164,16 +170,18 @@ def main(): except AttributeError: bes_conn = besapi.besapi.get_bes_conn_using_config_file() + export_folder = os.path.join(invoke_folder, "export") + # if --delete arg used, delete export folder: if args.delete: - shutil.rmtree("export", ignore_errors=True) + shutil.rmtree(export_folder, ignore_errors=True) try: - os.mkdir("export") + os.mkdir(export_folder) except FileExistsError: logging.warning("Folder already exists!") - os.chdir("export") + os.chdir(export_folder) bes_conn.export_all_sites() From 50ae2070a01fc6e4fd7aac30a05ef2befc177ef8 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 7 Aug 2024 23:23:49 -0400 Subject: [PATCH 133/231] tweak send message. --- examples/send_message_all_computers.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/send_message_all_computers.py b/examples/send_message_all_computers.py index 54489af..9cff3b3 100644 --- a/examples/send_message_all_computers.py +++ b/examples/send_message_all_computers.py @@ -1,17 +1,24 @@ +""" +This will send a BigFix UI message to ALL computers! +""" + import besapi -CONTENT_XML = r""" +MESSAGE_TITLE = """Test message from besapi""" +MESSAGE = MESSAGE_TITLE + +CONTENT_XML = rf""" - Test message from besapi + Send Message: {MESSAGE_TITLE} = ("3.1.0" as version)) of key "HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall" of x32 registry) else if (mac of operating system) then (exists application "BigFixSSA.app" whose (version of it >= "3.1.0")) else false) AND (exists line whose (it = "disableMessagesTab: false") of file (if (windows of operating system) then (pathname of parent folder of parent folder of client) & "\BigFix Self Service Application\resources\ssa.config" else "/Library/Application Support/BigFix/BigFixSSA/ssa.config"))]]> //Nothing to do - Test message from besapi + {MESSAGE_TITLE} true - Test message from besapi

]]>
+ {MESSAGE}

]]>
false false false @@ -87,7 +94,7 @@ action-ui-metadata - {"type":"notification","sender":"broadcast","expirationDays":3} + {{"type":"notification","sender":"broadcast","expirationDays":3}}
From c29ba8aaafd65b3d02ca56e36294d197f367e704 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 7 Aug 2024 23:42:01 -0400 Subject: [PATCH 134/231] tweak client query --- examples/client_query_from_string.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/client_query_from_string.py b/examples/client_query_from_string.py index 5445160..0298043 100644 --- a/examples/client_query_from_string.py +++ b/examples/client_query_from_string.py @@ -10,6 +10,8 @@ import besapi +CLIENT_RELEVANCE = "(computer names, model name of main processor, (it as string) of (it / (1024 * 1024 * 1024)) of total amount of ram)" + def main(): """Execution starts here""" @@ -34,7 +36,7 @@ def main(): # print(item) # this is the client relevance we are going to get the results of: - client_relevance = "(computer names, operating systems)" + client_relevance = CLIENT_RELEVANCE # generate target XML substring from list of computer ids: target_xml = ( @@ -93,7 +95,7 @@ def main(): print("not interactive, stopping loop") break except KeyboardInterrupt: - print("loop interuppted") + print("\nloop interuppted") print("script finished") From 9d39d58d270d9a158a106c84de38e28f6f65942b Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 7 Aug 2024 23:45:04 -0400 Subject: [PATCH 135/231] tweak code to make it easier --- examples/baseline_by_relevance.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/baseline_by_relevance.py b/examples/baseline_by_relevance.py index fcd5c4d..f927cad 100644 --- a/examples/baseline_by_relevance.py +++ b/examples/baseline_by_relevance.py @@ -9,6 +9,8 @@ import besapi +FIXLET_RELEVANCE = 'fixlets whose(name of it starts with "Update:") of bes sites whose( external site flag of it AND name of it = "Updates for Windows Applications Extended" )' + def main(): """Execution starts here""" @@ -19,7 +21,7 @@ def main(): print(bes_conn.last_connected) # change the relevance here to adjust which content gets put in a baseline: - fixlets_rel = 'fixlets whose(name of it starts with "Update:") of bes sites whose( external site flag of it AND name of it = "Updates for Windows Applications Extended" )' + fixlets_rel = FIXLET_RELEVANCE # this gets the info needed from the items to make the baseline: session_relevance = f"""(it as string) of (url of site of it, ids of it, content id of default action of it | "Action1") of it whose(exists default action of it AND globally visible flag of it AND name of it does not contain "(Superseded)" AND exists applicable computers whose(now - last report time of it < 60 * day) of it) of {fixlets_rel}""" From 6a15b155b2d69c78a183a5f11fa5a070dc3cde5a Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 7 Aug 2024 23:51:34 -0400 Subject: [PATCH 136/231] make it easier to wake --- examples/wake_on_lan.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/examples/wake_on_lan.py b/examples/wake_on_lan.py index 2a98862..420d1ae 100644 --- a/examples/wake_on_lan.py +++ b/examples/wake_on_lan.py @@ -5,6 +5,7 @@ Related: +- https://support.hcltechsw.com/csm?id=kb_article&sysparm_article=KB0023378 - http://localhost:__WebReportsPort__/json/wakeonlan?cid=_ComputerID_&cid=_NComputerID_ - POST(binary) http://localhost:52311/data/wake-on-lan - https://localhost:52311/rd-proxy?RequestUrl=cgi-bin/bfenterprise/BESGatherMirrorNew.exe/-triggergatherdb?forwardtrigger @@ -16,6 +17,11 @@ import besapi +SESSION_RELEVANCE_COMPUTER_IDS = """ + ids of bes computers + whose(root server flag of it AND now - last report time of it < 10 * day) +""" + def main(): """Execution starts here""" @@ -27,10 +33,7 @@ def main(): # SessionRelevance for computer ids you wish to wake: # this currently returns the root server itself, which should have no real effect. # change this to a singular or plural result of computer ids you wish to wake. - session_relevance = """ - ids of bes computers - whose(root server flag of it AND now - last report time of it < 10 * day) - """ + session_relevance = SESSION_RELEVANCE_COMPUTER_IDS computer_id_array = bes_conn.session_relevance_array(session_relevance) From f75056cef128d352bb933dfc58e85c8a3a938447 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 8 Aug 2024 14:58:38 -0400 Subject: [PATCH 137/231] tweak the script --- examples/wake_on_lan.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/wake_on_lan.py b/examples/wake_on_lan.py index 420d1ae..94d8e7a 100644 --- a/examples/wake_on_lan.py +++ b/examples/wake_on_lan.py @@ -82,7 +82,9 @@ def main(): """ ) - result = bes_conn.session.post(f"{bes_conn.rootserver}/WakeOnLan", data=soap_xml) + result = bes_conn.session.post( + f"{bes_conn.rootserver}/WakeOnLan", data=soap_xml, verify=False + ) print(result) print(result.text) From 93fa58f9ee985c52182ef26a8b9e0433c9efee95 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 21 Aug 2024 17:09:02 -0400 Subject: [PATCH 138/231] improve error handling --- examples/baseline_by_relevance.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/baseline_by_relevance.py b/examples/baseline_by_relevance.py index f927cad..bc8ade0 100644 --- a/examples/baseline_by_relevance.py +++ b/examples/baseline_by_relevance.py @@ -37,8 +37,12 @@ def main(): for item in result: # print(item) tuple_items = item.split(", ") - baseline_components += f""" - """ + try: + baseline_components += f""" + """ + except IndexError: + print("ERROR: a component was missing a key item.") + continue # print(baseline_components) From 95210f326c920f397597c8f498d513a0bb5314cd Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 27 Aug 2024 13:37:34 -0400 Subject: [PATCH 139/231] add utility functions to make creating bigfix plugins easier --- src/besapi/plugin_utilities.py | 132 +++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 src/besapi/plugin_utilities.py diff --git a/src/besapi/plugin_utilities.py b/src/besapi/plugin_utilities.py new file mode 100644 index 0000000..0feb509 --- /dev/null +++ b/src/besapi/plugin_utilities.py @@ -0,0 +1,132 @@ +"""This is a set of utility functions for use in multiple plugins""" + +import argparse +import os +import logging +import logging.handlers +import ntpath +import sys + + +def get_invoke_folder(verbose=0): + """Get the folder the script was invoked from""" + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_folder = os.path.abspath(os.path.dirname(sys.executable)) + else: + if verbose: + print("running in a normal Python process") + invoke_folder = os.path.abspath(os.path.dirname(__file__)) + + if verbose: + print(f"invoke_folder = {invoke_folder}") + + return invoke_folder + + +def get_invoke_file_name(verbose=0): + """Get the filename the script was invoked from""" + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_file_path = sys.executable + else: + if verbose: + print("running in a normal Python process") + invoke_file_path = __file__ + + if verbose: + print(f"invoke_file_path = {invoke_file_path}") + + # get just the file name, return without file extension: + return os.path.splitext(ntpath.basename(invoke_file_path))[0] + + +def setup_plugin_argparse(plugin_args_required=False): + """setup argparse for plugin use""" + arg_parser = argparse.ArgumentParser( + description="Provde command line arguments for REST URL, username, and password" + ) + arg_parser.add_argument( + "-v", + "--verbose", + help="Set verbose output", + required=False, + action="count", + default=0, + ) + arg_parser.add_argument( + "-c", + "--console", + help="log output to console", + required=False, + action="store_true", + ) + arg_parser.add_argument( + "-besserver", "--besserver", help="Specify the BES URL", required=False + ) + arg_parser.add_argument( + "-r", "--rest-url", help="Specify the REST URL", required=plugin_args_required + ) + arg_parser.add_argument( + "-u", "--user", help="Specify the username", required=plugin_args_required + ) + arg_parser.add_argument( + "-p", "--password", help="Specify the password", required=False + ) + + return arg_parser + + +def setup_plugin_logging(log_file_name="", verbose=0, console=True): + """setup logging for plugin use""" + # get folder the script was invoked from: + invoke_folder = get_invoke_folder(verbose) + + if not log_file_name or log_file_name == "": + log_file_name = get_invoke_file_name() + ".log" + + # set different log levels: + log_level = logging.WARNING + if verbose: + log_level = logging.INFO + if verbose > 1: + log_level = logging.DEBUG + + # get path to put log file in: + log_filename = os.path.join(invoke_folder, log_file_name) + + handlers = [ + logging.handlers.RotatingFileHandler( + log_filename, maxBytes=5 * 1024 * 1024, backupCount=1 + ) + ] + + # log output to console if arg provided: + if console: + handlers.append(logging.StreamHandler()) + + # setup logging: + logging.basicConfig( + encoding="utf-8", + level=log_level, + format="%(asctime)s %(levelname)s:%(message)s", + handlers=handlers, + ) + + +# if __name__ == "__main__": +# print(get_invoke_folder()) +# print(get_invoke_file_name()) +# setup_plugin_logging(console=True) +# logging.error("test logging") +# parser = setup_plugin_argparse() +# # allow unknown args to be parsed instead of throwing an error: +# args, _unknown = parser.parse_known_args() + +# logging.error(args) From 73b5642c5112b7c910e9b50a6a6ffd19da271297 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 27 Aug 2024 13:43:53 -0400 Subject: [PATCH 140/231] fix test build --- .github/workflows/test_build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index e3042b0..ced3877 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -28,7 +28,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, windows-latest, macos-13] # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json python-version: ["3.7", "3"] steps: From 152f4e660a476ed5933cc1d5f4c69cf7d721a54e Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 27 Aug 2024 13:46:30 -0400 Subject: [PATCH 141/231] fix isort error, fix missing shell:bash --- .github/workflows/test_build.yaml | 2 ++ src/besapi/plugin_utilities.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index ced3877..1d8b49b 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -40,10 +40,12 @@ jobs: - name: Install build tools run: pip install setuptools wheel build pyinstaller - name: Install requirements + shell: bash run: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Read VERSION file id: getversion + shell: bash run: echo "$(python ./setup.py --version)" - name: Run Tests - Source diff --git a/src/besapi/plugin_utilities.py b/src/besapi/plugin_utilities.py index 0feb509..8f2bfe2 100644 --- a/src/besapi/plugin_utilities.py +++ b/src/besapi/plugin_utilities.py @@ -1,10 +1,10 @@ """This is a set of utility functions for use in multiple plugins""" import argparse -import os import logging import logging.handlers import ntpath +import os import sys From 2f40812648da79952a767e058dc3a2b1a424f81a Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 27 Aug 2024 13:49:22 -0400 Subject: [PATCH 142/231] update besapi version, cut release --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 150fc98..e1f04cd 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.2.7" +__version__ = "3.3.1" besapi_logger = logging.getLogger("besapi") From 04fec57cc86f6244a9a97568b526fcce26c42d80 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 27 Aug 2024 14:03:56 -0400 Subject: [PATCH 143/231] test publishing fix --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index e1f04cd..eebd89e 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.3.1" +__version__ = "3.3.2" besapi_logger = logging.getLogger("besapi") From bc7e78b7e4ca1bbbc73b8ce948a4acad5b499c4a Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 27 Aug 2024 14:31:21 -0400 Subject: [PATCH 144/231] enhance plugin_utilities --- src/besapi/plugin_utilities.py | 52 ++++++++++++++++++++++++++++------ tests/tests.py | 9 ++++++ 2 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/besapi/plugin_utilities.py b/src/besapi/plugin_utilities.py index 8f2bfe2..7c4e3d3 100644 --- a/src/besapi/plugin_utilities.py +++ b/src/besapi/plugin_utilities.py @@ -6,6 +6,9 @@ import ntpath import os import sys +import getpass + +import besapi def get_invoke_folder(verbose=0): @@ -120,13 +123,44 @@ def setup_plugin_logging(log_file_name="", verbose=0, console=True): ) -# if __name__ == "__main__": -# print(get_invoke_folder()) -# print(get_invoke_file_name()) -# setup_plugin_logging(console=True) -# logging.error("test logging") -# parser = setup_plugin_argparse() -# # allow unknown args to be parsed instead of throwing an error: -# args, _unknown = parser.parse_known_args() +def get_besapi_connection(args): + """get connection to besapi using either args or config file if args not provided""" + + password = args.password + + # if user was provided as arg but password was not: + if args.user and not password: + logging.warning("Password was not provided, provide REST API password.") + print("Password was not provided, provide REST API password:") + password = getpass.getpass() + + # process args, setup connection: + rest_url = args.rest_url + + # normalize url to https://HostOrIP:52311 + if rest_url and rest_url.endswith("/api"): + rest_url = rest_url.replace("/api", "") + + # attempt bigfix connection with provided args: + if args.user and password: + try: + bes_conn = besapi.besapi.BESConnection(args.user, password, rest_url) + # bes_conn.login() + except ( + AttributeError, + ConnectionRefusedError, + besapi.besapi.requests.exceptions.ConnectionError, + ): + try: + # print(args.besserver) + bes_conn = besapi.besapi.BESConnection( + args.user, password, args.besserver + ) + # handle case where args.besserver is None + # AttributeError: 'NoneType' object has no attribute 'startswith' + except AttributeError: + bes_conn = besapi.besapi.get_bes_conn_using_config_file() + else: + bes_conn = besapi.besapi.get_bes_conn_using_config_file() -# logging.error(args) + return bes_conn diff --git a/tests/tests.py b/tests/tests.py index 45856f6..a7745dc 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -25,6 +25,7 @@ sys.path.reverse() import besapi +import besapi.plugin_utilities print("besapi version: " + str(besapi.besapi.__version__)) @@ -151,4 +152,12 @@ class RequestResult(object): bes_conn = besapi.besapi.get_bes_conn_using_config_file() print("login succeeded:", bes_conn.login()) +# test plugin_utilities: +print(besapi.plugin_utilities.get_invoke_folder()) +print(besapi.plugin_utilities.get_invoke_file_name()) +besapi.plugin_utilities.setup_plugin_logging(console=True) +parser = besapi.plugin_utilities.setup_plugin_argparse(plugin_args_required=False) +# allow unknown args to be parsed instead of throwing an error: +args, _unknown = parser.parse_known_args() + sys.exit(0) From 98f3fe9330eb92ca7e633038807b28636ec8d241 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 27 Aug 2024 14:35:10 -0400 Subject: [PATCH 145/231] fix isort, fix tests --- src/besapi/plugin_utilities.py | 2 +- tests/tests.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/besapi/plugin_utilities.py b/src/besapi/plugin_utilities.py index 7c4e3d3..80c703b 100644 --- a/src/besapi/plugin_utilities.py +++ b/src/besapi/plugin_utilities.py @@ -1,12 +1,12 @@ """This is a set of utility functions for use in multiple plugins""" import argparse +import getpass import logging import logging.handlers import ntpath import os import sys -import getpass import besapi diff --git a/tests/tests.py b/tests/tests.py index a7745dc..37d506b 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -155,7 +155,8 @@ class RequestResult(object): # test plugin_utilities: print(besapi.plugin_utilities.get_invoke_folder()) print(besapi.plugin_utilities.get_invoke_file_name()) -besapi.plugin_utilities.setup_plugin_logging(console=True) +# the following doesn't seem to work in python 3.7: +# besapi.plugin_utilities.setup_plugin_logging(console=True) parser = besapi.plugin_utilities.setup_plugin_argparse(plugin_args_required=False) # allow unknown args to be parsed instead of throwing an error: args, _unknown = parser.parse_known_args() From 936bcb1e54b32ac186a9699d3880b50c08109156 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 27 Aug 2024 14:38:48 -0400 Subject: [PATCH 146/231] adding more utility functions for plugins --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index eebd89e..89ae7c6 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.3.2" +__version__ = "3.3.3" besapi_logger = logging.getLogger("besapi") From 0169e8ed273e8a5949b62d6009fb71e5fd3c302e Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 27 Aug 2024 14:43:58 -0400 Subject: [PATCH 147/231] update export_all_sites to use new plugin_utilities to simplify --- examples/export_all_sites.py | 124 ++++------------------------------- 1 file changed, 11 insertions(+), 113 deletions(-) diff --git a/examples/export_all_sites.py b/examples/export_all_sites.py index 9e37b1c..cd84b89 100644 --- a/examples/export_all_sites.py +++ b/examples/export_all_sites.py @@ -24,6 +24,7 @@ import sys import besapi +import besapi.plugin_utilities __version__ = "0.0.1" verbose = 0 @@ -31,64 +32,15 @@ invoke_folder = None -def get_invoke_folder(): - """Get the folder the script was invoked from - - References: - - https://github.com/jgstew/tools/blob/master/Python/locate_self.py - """ - # using logging here won't actually log it to the file: - - if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): - if verbose: - print("running in a PyInstaller bundle") - invoke_folder = os.path.abspath(os.path.dirname(sys.executable)) - else: - if verbose: - print("running in a normal Python process") - invoke_folder = os.path.abspath(os.path.dirname(__file__)) - - if verbose: - print(f"invoke_folder = {invoke_folder}") - - return invoke_folder - - -def test_file_exists(path): - """return true if file exists""" - - if not (os.path.isfile(path) and os.access(path, os.R_OK)): - path = os.path.join(invoke_folder, path) - - logging.info("testing if exists: `%s`", path) - - if os.path.isfile(path) and os.access(path, os.R_OK) and os.access(path, os.W_OK): - return path - - return False - - def main(): """Execution starts here""" print("main() start") - parser = argparse.ArgumentParser( - description="Provde command line arguments for REST URL, username, and password" - ) - parser.add_argument( - "-v", - "--verbose", - help="Set verbose output", - required=False, - action="count", - default=0, - ) - parser.add_argument( - "-besserver", "--besserver", help="Specify the BES URL", required=False - ) - parser.add_argument("-r", "--rest-url", help="Specify the REST URL", required=False) - parser.add_argument("-u", "--user", help="Specify the username", required=False) - parser.add_argument("-p", "--password", help="Specify the password", required=False) + print("NOTE: this script requires besapi v3.3.3+") + + parser = besapi.plugin_utilities.setup_plugin_argparse() + + # add additonal arg specific to this script: parser.add_argument( "-d", "--delete", @@ -104,71 +56,17 @@ def main(): verbose = args.verbose # get folder the script was invoked from: - invoke_folder = get_invoke_folder() - - # set different log levels: - log_level = logging.INFO - if verbose: - log_level = logging.INFO - if verbose > 1: - log_level = logging.DEBUG - - # get path to put log file in: - log_filename = os.path.join(invoke_folder, "export_all_sites.log") - - print(f"Log File Path: {log_filename}") - - handlers = [ - logging.handlers.RotatingFileHandler( - log_filename, maxBytes=5 * 1024 * 1024, backupCount=1 - ) - ] - - # log output to console: - handlers.append(logging.StreamHandler()) - - # setup logging: - logging.basicConfig( - encoding="utf-8", - level=log_level, - format="%(asctime)s %(levelname)s:%(message)s", - handlers=handlers, - ) + invoke_folder = besapi.plugin_utilities.get_invoke_folder() + + besapi.plugin_utilities.setup_plugin_logging() + logging.info("----- Starting New Session ------") logging.debug("invoke folder: %s", invoke_folder) logging.debug("Python version: %s", platform.sys.version) logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) logging.debug("this plugin's version: %s", __version__) - password = args.password - - if not password: - logging.warning("Password was not provided, provide REST API password.") - print("Password was not provided, provide REST API password.") - password = getpass.getpass() - - # process args, setup connection: - rest_url = args.rest_url - - # normalize url to https://HostOrIP:52311 - if rest_url and rest_url.endswith("/api"): - rest_url = rest_url.replace("/api", "") - - try: - bes_conn = besapi.besapi.BESConnection(args.user, password, rest_url) - # bes_conn.login() - except ( - AttributeError, - ConnectionRefusedError, - besapi.besapi.requests.exceptions.ConnectionError, - ): - try: - # print(args.besserver) - bes_conn = besapi.besapi.BESConnection(args.user, password, args.besserver) - # handle case where args.besserver is None - # AttributeError: 'NoneType' object has no attribute 'startswith' - except AttributeError: - bes_conn = besapi.besapi.get_bes_conn_using_config_file() + bes_conn = besapi.plugin_utilities.get_besapi_connection(args) export_folder = os.path.join(invoke_folder, "export") From 1648a391252ce294be4c153d71c65aaa4bf468cd Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 28 Aug 2024 12:54:13 -0400 Subject: [PATCH 148/231] add comment --- examples/baseline_by_relevance.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/baseline_by_relevance.py b/examples/baseline_by_relevance.py index bc8ade0..0b16634 100644 --- a/examples/baseline_by_relevance.py +++ b/examples/baseline_by_relevance.py @@ -9,6 +9,7 @@ import besapi +# This relevance string must start with `fixlets` and return the set of fixlets you wish to turn into a baseline FIXLET_RELEVANCE = 'fixlets whose(name of it starts with "Update:") of bes sites whose( external site flag of it AND name of it = "Updates for Windows Applications Extended" )' From 578282a595a5752896c60b4aa612918702b62a13 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 28 Aug 2024 17:47:00 -0400 Subject: [PATCH 149/231] add better logging, better logic --- src/besapi/plugin_utilities.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/besapi/plugin_utilities.py b/src/besapi/plugin_utilities.py index 80c703b..6190e71 100644 --- a/src/besapi/plugin_utilities.py +++ b/src/besapi/plugin_utilities.py @@ -134,6 +134,9 @@ def get_besapi_connection(args): print("Password was not provided, provide REST API password:") password = getpass.getpass() + if args.user: + logging.debug("REST API Password Length: %s", len(password)) + # process args, setup connection: rest_url = args.rest_url @@ -145,12 +148,16 @@ def get_besapi_connection(args): if args.user and password: try: bes_conn = besapi.besapi.BESConnection(args.user, password, rest_url) - # bes_conn.login() except ( AttributeError, ConnectionRefusedError, besapi.besapi.requests.exceptions.ConnectionError, ): + logging.exception( + "connection to `%s` failed, attempting `%s` instead", + rest_url, + args.besserver, + ) try: # print(args.besserver) bes_conn = besapi.besapi.BESConnection( @@ -159,8 +166,22 @@ def get_besapi_connection(args): # handle case where args.besserver is None # AttributeError: 'NoneType' object has no attribute 'startswith' except AttributeError: - bes_conn = besapi.besapi.get_bes_conn_using_config_file() + logging.exception("----- ERROR: BigFix Connection Failed ------") + logging.exception( + "attempts to connect to BigFix using rest_url and besserver both failed" + ) + return None + except BaseException as err: + # always log error and stop the current process + logging.exception("ERROR: %s", err) + logging.exception( + "----- ERROR: BigFix Connection Failed! Unknown reason ------" + ) + return None else: + logging.info( + "attempting connection to BigFix using config file method as user command arg was not provided" + ) bes_conn = besapi.besapi.get_bes_conn_using_config_file() return bes_conn From 91ea1b8e9c4bebfc08066e3ade3a2cb1b030e913 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 15:42:32 -0400 Subject: [PATCH 150/231] fix path for log file --- examples/export_all_sites.py | 46 ++++++++++++++++++++++++++++++++-- src/besapi/plugin_utilities.py | 16 ++++++------ 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/examples/export_all_sites.py b/examples/export_all_sites.py index cd84b89..2008202 100644 --- a/examples/export_all_sites.py +++ b/examples/export_all_sites.py @@ -19,6 +19,7 @@ import logging import logging.handlers import os +import ntpath import platform import shutil import sys @@ -32,6 +33,45 @@ invoke_folder = None +def get_invoke_folder(verbose=0): + """Get the folder the script was invoked from""" + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_folder = os.path.abspath(os.path.dirname(sys.executable)) + else: + if verbose: + print("running in a normal Python process") + invoke_folder = os.path.abspath(os.path.dirname(__file__)) + + if verbose: + print(f"invoke_folder = {invoke_folder}") + + return invoke_folder + + +def get_invoke_file_name(verbose=0): + """Get the filename the script was invoked from""" + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_file_path = sys.executable + else: + if verbose: + print("running in a normal Python process") + invoke_file_path = __file__ + + if verbose: + print(f"invoke_file_path = {invoke_file_path}") + + # get just the file name, return without file extension: + return os.path.splitext(ntpath.basename(invoke_file_path))[0] + + def main(): """Execution starts here""" print("main() start") @@ -56,9 +96,11 @@ def main(): verbose = args.verbose # get folder the script was invoked from: - invoke_folder = besapi.plugin_utilities.get_invoke_folder() + invoke_folder = get_invoke_folder() + + log_file_path = invoke_folder + get_invoke_file_name() + ".log" - besapi.plugin_utilities.setup_plugin_logging() + besapi.plugin_utilities.setup_plugin_logging(log_file_path) logging.info("----- Starting New Session ------") logging.debug("invoke folder: %s", invoke_folder) diff --git a/src/besapi/plugin_utilities.py b/src/besapi/plugin_utilities.py index 6190e71..5c8d030 100644 --- a/src/besapi/plugin_utilities.py +++ b/src/besapi/plugin_utilities.py @@ -86,27 +86,25 @@ def setup_plugin_argparse(plugin_args_required=False): return arg_parser -def setup_plugin_logging(log_file_name="", verbose=0, console=True): +def setup_plugin_logging(log_file_path="", verbose=0, console=True): """setup logging for plugin use""" - # get folder the script was invoked from: - invoke_folder = get_invoke_folder(verbose) - if not log_file_name or log_file_name == "": - log_file_name = get_invoke_file_name() + ".log" + if not log_file_path or log_file_path == "": + log_file_path = os.path.join( + get_invoke_folder(verbose), get_invoke_file_name() + ".log" + ) # set different log levels: log_level = logging.WARNING if verbose: log_level = logging.INFO + print("INFO: Log File Path: %s", log_file_path) if verbose > 1: log_level = logging.DEBUG - # get path to put log file in: - log_filename = os.path.join(invoke_folder, log_file_name) - handlers = [ logging.handlers.RotatingFileHandler( - log_filename, maxBytes=5 * 1024 * 1024, backupCount=1 + log_file_path, maxBytes=5 * 1024 * 1024, backupCount=1 ) ] From 8871be309ec76e5bcf28ca4c0813d8dddf25c694 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 15:46:33 -0400 Subject: [PATCH 151/231] fix isort --- .pre-commit-config.yaml | 4 ++-- examples/export_all_sites.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fcb1cdc..3177af5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,7 +6,7 @@ # https://github.com/pre-commit/pre-commit-hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-yaml - id: check-json @@ -36,6 +36,6 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black - rev: 24.2.0 + rev: 24.8.0 hooks: - id: black diff --git a/examples/export_all_sites.py b/examples/export_all_sites.py index 2008202..cec444b 100644 --- a/examples/export_all_sites.py +++ b/examples/export_all_sites.py @@ -18,8 +18,8 @@ import getpass import logging import logging.handlers -import os import ntpath +import os import platform import shutil import sys From 3a2d07374df65b970bb8f832e7352ecaf910092a Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 15:49:30 -0400 Subject: [PATCH 152/231] new release --- src/besapi/besapi.py | 2 +- src/besapi/plugin_utilities.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 89ae7c6..6957021 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.3.3" +__version__ = "3.4.1" besapi_logger = logging.getLogger("besapi") diff --git a/src/besapi/plugin_utilities.py b/src/besapi/plugin_utilities.py index 5c8d030..938531d 100644 --- a/src/besapi/plugin_utilities.py +++ b/src/besapi/plugin_utilities.py @@ -91,7 +91,7 @@ def setup_plugin_logging(log_file_path="", verbose=0, console=True): if not log_file_path or log_file_path == "": log_file_path = os.path.join( - get_invoke_folder(verbose), get_invoke_file_name() + ".log" + get_invoke_folder(verbose), get_invoke_file_name(verbose) + ".log" ) # set different log levels: From c71cf30948c230d30ee0d7cd9fe303fb79826e75 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 16:15:44 -0400 Subject: [PATCH 153/231] tweak plugin utils for logging --- examples/export_all_sites.py | 6 +++++- src/besapi/plugin_utilities.py | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/export_all_sites.py b/examples/export_all_sites.py index cec444b..b80774f 100644 --- a/examples/export_all_sites.py +++ b/examples/export_all_sites.py @@ -98,7 +98,11 @@ def main(): # get folder the script was invoked from: invoke_folder = get_invoke_folder() - log_file_path = invoke_folder + get_invoke_file_name() + ".log" + log_file_path = os.path.join( + get_invoke_folder(verbose), get_invoke_file_name(verbose) + ".log" + ) + + print(log_file_path) besapi.plugin_utilities.setup_plugin_logging(log_file_path) diff --git a/src/besapi/plugin_utilities.py b/src/besapi/plugin_utilities.py index 938531d..89bfb01 100644 --- a/src/besapi/plugin_utilities.py +++ b/src/besapi/plugin_utilities.py @@ -11,6 +11,7 @@ import besapi +# NOTE: This does not work as expected when run from plugin_utilities def get_invoke_folder(verbose=0): """Get the folder the script was invoked from""" # using logging here won't actually log it to the file: @@ -30,6 +31,7 @@ def get_invoke_folder(verbose=0): return invoke_folder +# NOTE: This does not work as expected when run from plugin_utilities def get_invoke_file_name(verbose=0): """Get the filename the script was invoked from""" # using logging here won't actually log it to the file: @@ -98,7 +100,7 @@ def setup_plugin_logging(log_file_path="", verbose=0, console=True): log_level = logging.WARNING if verbose: log_level = logging.INFO - print("INFO: Log File Path: %s", log_file_path) + print("INFO: Log File Path:", log_file_path) if verbose > 1: log_level = logging.DEBUG @@ -111,6 +113,7 @@ def setup_plugin_logging(log_file_path="", verbose=0, console=True): # log output to console if arg provided: if console: handlers.append(logging.StreamHandler()) + print("INFO: also logging to console") # setup logging: logging.basicConfig( @@ -118,6 +121,7 @@ def setup_plugin_logging(log_file_path="", verbose=0, console=True): level=log_level, format="%(asctime)s %(levelname)s:%(message)s", handlers=handlers, + force=True, ) From 74846ca27a5a82be9eb0f3da72577a75f97cb5bb Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 16:19:54 -0400 Subject: [PATCH 154/231] update pre-commit, dependabot --- .github/dependabot.yml | 13 +++++++++++++ .pre-commit-config.yaml | 6 ++++++ 2 files changed, 19 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..24d901f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +# Set update schedule for GitHub Actions +version: 2 + +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file +updates: + # Maintain dependencies for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + # Add assignees + assignees: + - "jgstew" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3177af5..d11194c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,3 +39,9 @@ repos: rev: 24.8.0 hooks: - id: black + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.29.2 + hooks: + - id: check-github-workflows + args: ["--verbose"] + - id: check-dependabot From 4923d84f3c06b22912aef94c8472050f757de1d9 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 16:22:53 -0400 Subject: [PATCH 155/231] add pre-commit action, remove isort action --- .github/workflows/pre-commit.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/pre-commit.yaml diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml new file mode 100644 index 0000000..69c5117 --- /dev/null +++ b/.github/workflows/pre-commit.yaml @@ -0,0 +1,17 @@ +--- +name: pre-commit + +on: pull_request + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # requites to grab the history of the PR + fetch-depth: 0 + - uses: actions/setup-python@v5 + - uses: pre-commit/action@v3.0.1 + with: + extra_args: --color=always --from-ref ${{ github.event.pull_request.base.sha }} --to-ref ${{ github.event.pull_request.head.sha }} From 4685a39029edf4f7d83ca286d8b769a55768147c Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 16:24:21 -0400 Subject: [PATCH 156/231] fix pre-commit trigger --- .github/workflows/pre-commit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 69c5117..48320f8 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -1,7 +1,7 @@ --- name: pre-commit -on: pull_request +on: [push, pull_request] jobs: pre-commit: From e5f27e25bb9b26e2b836d1ce0f59411c6d765a0c Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 16:25:40 -0400 Subject: [PATCH 157/231] fix pre-commit trigger --- .github/workflows/pre-commit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-commit.yaml b/.github/workflows/pre-commit.yaml index 48320f8..69c5117 100644 --- a/.github/workflows/pre-commit.yaml +++ b/.github/workflows/pre-commit.yaml @@ -1,7 +1,7 @@ --- name: pre-commit -on: [push, pull_request] +on: pull_request jobs: pre-commit: From d9d88c0f69da797145c7b9d57db82c980f2b1d15 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 19:52:23 -0400 Subject: [PATCH 158/231] change logging function to return config --- src/besapi/plugin_utilities.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/besapi/plugin_utilities.py b/src/besapi/plugin_utilities.py index 89bfb01..b7381a9 100644 --- a/src/besapi/plugin_utilities.py +++ b/src/besapi/plugin_utilities.py @@ -115,14 +115,14 @@ def setup_plugin_logging(log_file_path="", verbose=0, console=True): handlers.append(logging.StreamHandler()) print("INFO: also logging to console") - # setup logging: - logging.basicConfig( - encoding="utf-8", - level=log_level, - format="%(asctime)s %(levelname)s:%(message)s", - handlers=handlers, - force=True, - ) + # return logging config: + return { + "encoding": "utf-8", + "level": log_level, + "format": "%(asctime)s %(levelname)s:%(message)s", + "handlers": handlers, + "force": True, + } def get_besapi_connection(args): From 1f35bd28d9ece211aac5b78fa0f106b9d4fc01fe Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 19:54:33 -0400 Subject: [PATCH 159/231] new release --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 6957021..f904b4f 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.4.1" +__version__ = "3.4.2" besapi_logger = logging.getLogger("besapi") From d433e024ce91e680b1d2a7803d986b0544cdac7f Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 20:15:42 -0400 Subject: [PATCH 160/231] rename logging config function --- examples/export_all_sites.py | 6 +++++- src/besapi/besapi.py | 2 +- src/besapi/plugin_utilities.py | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/export_all_sites.py b/examples/export_all_sites.py index b80774f..1098704 100644 --- a/examples/export_all_sites.py +++ b/examples/export_all_sites.py @@ -104,7 +104,11 @@ def main(): print(log_file_path) - besapi.plugin_utilities.setup_plugin_logging(log_file_path) + logging_config = besapi.plugin_utilities.get_plugin_logging_config( + log_file_path, verbose, args.console + ) + + logging.basicConfig(**logging_config) logging.info("----- Starting New Session ------") logging.debug("invoke folder: %s", invoke_folder) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index f904b4f..6171fe9 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.4.2" +__version__ = "3.5.1" besapi_logger = logging.getLogger("besapi") diff --git a/src/besapi/plugin_utilities.py b/src/besapi/plugin_utilities.py index b7381a9..d04103d 100644 --- a/src/besapi/plugin_utilities.py +++ b/src/besapi/plugin_utilities.py @@ -88,8 +88,8 @@ def setup_plugin_argparse(plugin_args_required=False): return arg_parser -def setup_plugin_logging(log_file_path="", verbose=0, console=True): - """setup logging for plugin use""" +def get_plugin_logging_config(log_file_path="", verbose=0, console=True): + """get config for logging for plugin use""" if not log_file_path or log_file_path == "": log_file_path = os.path.join( From 67090c7c6cbd4e6847789ea138cdcc5d17620bd4 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 20:44:08 -0400 Subject: [PATCH 161/231] add better error handling --- src/besapi/plugin_utilities.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/besapi/plugin_utilities.py b/src/besapi/plugin_utilities.py index d04103d..27303e8 100644 --- a/src/besapi/plugin_utilities.py +++ b/src/besapi/plugin_utilities.py @@ -1,4 +1,7 @@ -"""This is a set of utility functions for use in multiple plugins""" +"""This is a set of utility functions for use in multiple plugins + +see example here: https://github.com/jgstew/besapi/blob/master/examples/export_all_sites.py +""" import argparse import getpass @@ -89,7 +92,9 @@ def setup_plugin_argparse(plugin_args_required=False): def get_plugin_logging_config(log_file_path="", verbose=0, console=True): - """get config for logging for plugin use""" + """get config for logging for plugin use + + use this like: logging.basicConfig(**logging_config)""" if not log_file_path or log_file_path == "": log_file_path = os.path.join( @@ -149,6 +154,8 @@ def get_besapi_connection(args): # attempt bigfix connection with provided args: if args.user and password: try: + if not rest_url: + raise AttributeError bes_conn = besapi.besapi.BESConnection(args.user, password, rest_url) except ( AttributeError, @@ -161,7 +168,8 @@ def get_besapi_connection(args): args.besserver, ) try: - # print(args.besserver) + if not args.besserver: + raise AttributeError bes_conn = besapi.besapi.BESConnection( args.user, password, args.besserver ) From 058cb25558cf2e3961ed2ee40aa1800f8364f0a0 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 20:45:54 -0400 Subject: [PATCH 162/231] test get logging config --- tests/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index 37d506b..912dac5 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -156,7 +156,7 @@ class RequestResult(object): print(besapi.plugin_utilities.get_invoke_folder()) print(besapi.plugin_utilities.get_invoke_file_name()) # the following doesn't seem to work in python 3.7: -# besapi.plugin_utilities.setup_plugin_logging(console=True) +logging_config = besapi.plugin_utilities.get_plugin_logging_config() parser = besapi.plugin_utilities.setup_plugin_argparse(plugin_args_required=False) # allow unknown args to be parsed instead of throwing an error: args, _unknown = parser.parse_known_args() From 60e652092a74b56a07418cb870ba545f7d8619db Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 20:56:46 -0400 Subject: [PATCH 163/231] improved tests --- tests/tests.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/tests.py b/tests/tests.py index 912dac5..6f5ca49 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -155,10 +155,19 @@ class RequestResult(object): # test plugin_utilities: print(besapi.plugin_utilities.get_invoke_folder()) print(besapi.plugin_utilities.get_invoke_file_name()) -# the following doesn't seem to work in python 3.7: -logging_config = besapi.plugin_utilities.get_plugin_logging_config() + parser = besapi.plugin_utilities.setup_plugin_argparse(plugin_args_required=False) # allow unknown args to be parsed instead of throwing an error: args, _unknown = parser.parse_known_args() +# test logging plugin_utilities: +import logging + +logging_config = besapi.plugin_utilities.get_plugin_logging_config("./tests.log") +logging.basicConfig(**logging_config) + +logging.warning("Just testing to see if logging is working!") + +assert os.path.isfile("./tests.log") + sys.exit(0) From c36bd09cc99ae1e12837e303f15e5ea455754fec Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 21:02:47 -0400 Subject: [PATCH 164/231] fix tests, change min python version --- setup.cfg | 2 +- tests/tests.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/setup.cfg b/setup.cfg index b83a2e4..fcc2420 100644 --- a/setup.cfg +++ b/setup.cfg @@ -10,4 +10,4 @@ classifiers = License :: OSI Approved :: MIT License [options] -python_requires = >=3.6 +python_requires = >=3.7 diff --git a/tests/tests.py b/tests/tests.py index 6f5ca49..2c8b371 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -164,10 +164,10 @@ class RequestResult(object): import logging logging_config = besapi.plugin_utilities.get_plugin_logging_config("./tests.log") -logging.basicConfig(**logging_config) +# logging.basicConfig(**logging_config) -logging.warning("Just testing to see if logging is working!") +# logging.warning("Just testing to see if logging is working!") -assert os.path.isfile("./tests.log") +# assert os.path.isfile("./tests.log") sys.exit(0) From ba76f20026e32be6dfebc01da33a7f23dd9d7436 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 21:06:22 -0400 Subject: [PATCH 165/231] enhance tests --- tests/tests.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/tests.py b/tests/tests.py index 2c8b371..05d7e31 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -164,10 +164,13 @@ class RequestResult(object): import logging logging_config = besapi.plugin_utilities.get_plugin_logging_config("./tests.log") -# logging.basicConfig(**logging_config) -# logging.warning("Just testing to see if logging is working!") +# this use of logging.basicConfig requires python >= 3.9 +if sys.version_info >= (3, 9): + logging.basicConfig(**logging_config) -# assert os.path.isfile("./tests.log") + logging.warning("Just testing to see if logging is working!") + + assert os.path.isfile("./tests.log") sys.exit(0) From 0504c82276cd287859d3a44c9dd3ba070ad76476 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 21:13:20 -0400 Subject: [PATCH 166/231] tweak tests --- tests/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/tests.py b/tests/tests.py index 05d7e31..7280330 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -172,5 +172,6 @@ class RequestResult(object): logging.warning("Just testing to see if logging is working!") assert os.path.isfile("./tests.log") + os.remove("./tests.log") sys.exit(0) From dc44f187d02c8754fe8a62e7d6653868e298f228 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 21:15:35 -0400 Subject: [PATCH 167/231] fix tests --- tests/tests.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/tests.py b/tests/tests.py index 7280330..05d7e31 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -172,6 +172,5 @@ class RequestResult(object): logging.warning("Just testing to see if logging is working!") assert os.path.isfile("./tests.log") - os.remove("./tests.log") sys.exit(0) From 31a6afff8d8c40e387a15683f0576646c5affb74 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 21:23:03 -0400 Subject: [PATCH 168/231] update version to reflect new minimum python version --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 6171fe9..2af3301 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.5.1" +__version__ = "3.7.1" besapi_logger = logging.getLogger("besapi") From 05f003637d18f01209723744a46cdb5fbeefdcca Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 29 Aug 2024 21:25:01 -0400 Subject: [PATCH 169/231] update example version --- examples/export_all_sites.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/export_all_sites.py b/examples/export_all_sites.py index 1098704..b98713a 100644 --- a/examples/export_all_sites.py +++ b/examples/export_all_sites.py @@ -27,7 +27,7 @@ import besapi import besapi.plugin_utilities -__version__ = "0.0.1" +__version__ = "1.1.1" verbose = 0 bes_conn = None invoke_folder = None From 62c96651477ebe522d3d9f3c7f9e457499d70f6a Mon Sep 17 00:00:00 2001 From: JGStew Date: Fri, 27 Sep 2024 12:28:42 -0400 Subject: [PATCH 170/231] Update besapi.py update new version --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 2af3301..121fc80 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.7.1" +__version__ = "3.7.2" besapi_logger = logging.getLogger("besapi") From 6ef394bc29e3c655652403fbf55b3de54e0f1cd7 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 16 Oct 2024 14:35:49 -0400 Subject: [PATCH 171/231] add gitconfig defaults --- .gitconfig | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .gitconfig diff --git a/.gitconfig b/.gitconfig new file mode 100644 index 0000000..17948d3 --- /dev/null +++ b/.gitconfig @@ -0,0 +1,6 @@ +[core] + hideDotFiles = true +[rebase] + autoStash = true +[pull] + rebase = true From 164720f27e4adc484951197abce99fdbf3d3e78f Mon Sep 17 00:00:00 2001 From: JGStew Date: Mon, 21 Oct 2024 14:24:10 -0400 Subject: [PATCH 172/231] add delete command, improve import command --- src/bescli/bescli.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/bescli/bescli.py b/src/bescli/bescli.py index 115b224..68b8be5 100644 --- a/src/bescli/bescli.py +++ b/src/bescli/bescli.py @@ -54,6 +54,9 @@ def __init__(self, **kwargs): def do_get(self, line): """Perform get request to BigFix server using provided api endpoint argument""" + # remove any extra whitespace + line = line.strip() + # Remove root server prefix: # if root server prefix is not removed # and root server is given as IP Address, @@ -80,6 +83,26 @@ def do_get(self, line): else: self.pfeedback("Not currently logged in. Type 'login'.") + def do_delete(self, line): + """Perform delete request to BigFix server using provided api endpoint argument""" + + # remove any extra whitespace + line = line.strip() + + # Remove root server prefix: + if "/api/" in line: + line = str(line).split("/api/", 1)[1] + self.pfeedback("get " + line) + + if self.bes_conn: + output_item = self.bes_conn.delete(line) + + print(output_item) + # print(output_item.besdict) + # print(output_item.besjson) + else: + self.pfeedback("Not currently logged in. Type 'login'.") + def do_post(self, statement): """post file as data to path""" print(statement) @@ -366,9 +389,13 @@ def do_export_all_sites(self, statement=None): def do_import_bes(self, statement): """import bes file""" - self.poutput(f"Import file: {statement.args}") + bes_file_path = str(statement.args).strip() + + site_path = self.bes_conn.get_current_site_path(None) + + self.poutput(f"Import file: {bes_file_path}") - self.poutput(self.bes_conn.import_bes_to_site(str(statement.args))) + self.poutput(self.bes_conn.import_bes_to_site(bes_file_path, site_path)) complete_upload = Cmd.path_complete From aa90fab3c95e559d33633050eabc616371f51812 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 22 Oct 2024 13:45:52 -0400 Subject: [PATCH 173/231] Update besapi.py new release --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 121fc80..9eee340 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.7.2" +__version__ = "3.7.4" besapi_logger = logging.getLogger("besapi") From aa0bdfdf6a1fc08b0281e5fa31b7a66139626115 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 21 Jan 2025 16:46:06 -0500 Subject: [PATCH 174/231] Update setup_server_plugin_service.py update comment --- examples/setup_server_plugin_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/setup_server_plugin_service.py b/examples/setup_server_plugin_service.py index 030bd61..0b33543 100644 --- a/examples/setup_server_plugin_service.py +++ b/examples/setup_server_plugin_service.py @@ -200,7 +200,7 @@ def main(): EnableWakeOnLAN_id, ) - # NOTE: Work in progress + # Build the XML for the Multi Action Group to setup the plugin service: XML_String_MultiActionGroup = f""" From 4f73614e68142e5b276f683537e89e1cffeebe08 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 28 Jan 2025 18:13:55 -0500 Subject: [PATCH 175/231] add pre-commit hook --- .pre-commit-config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d11194c..b76fb81 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,3 +45,7 @@ repos: - id: check-github-workflows args: ["--verbose"] - id: check-dependabot + - repo: meta + hooks: + - id: check-useless-excludes + - id: check-hooks-apply From d79115650cb4284306ddbc7711dda893e67795e6 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Feb 2025 12:55:53 -0500 Subject: [PATCH 176/231] add better error handling for session_relevance_array --- src/besapi/besapi.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 9eee340..78ed07a 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.7.4" +__version__ = "3.7.5" besapi_logger = logging.getLogger("besapi") @@ -345,8 +345,11 @@ def session_relevance_array(self, relevance, **kwargs): besapi_logger.info("Query did not return any results") else: besapi_logger.error("%s\n%s", err2, rel_result.text) + result.append("ERROR: " + rel_result.text) raise else: + besapi_logger.error("%s\n%s", err, rel_result.text) + result.append("ERROR: " + rel_result.text) raise return result From bf732ad5f062fcccf798a8a280a8a5af00608e06 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Feb 2025 12:58:49 -0500 Subject: [PATCH 177/231] moving up minimum python version test --- .github/workflows/test_build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index 1d8b49b..0b03faf 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -30,7 +30,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-13] # https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json - python-version: ["3.7", "3"] + python-version: ["3.9", "3"] steps: - uses: actions/checkout@v4 - name: Set up Python From 592b73422cf7a518b903040104e71936d2319249 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Feb 2025 13:01:29 -0500 Subject: [PATCH 178/231] new release --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 78ed07a..d13127e 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.7.5" +__version__ = "3.7.6" besapi_logger = logging.getLogger("besapi") From 6c807ff9dfdaed4990e758fc4d9e651827d24c4d Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Feb 2025 16:56:56 -0500 Subject: [PATCH 179/231] minor tweaks --- src/bescli/__main__.py | 3 ++- src/bescli/bescli.py | 7 +++++++ tests/tests.py | 6 ++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/bescli/__main__.py b/src/bescli/__main__.py index ff6901a..7c831cb 100644 --- a/src/bescli/__main__.py +++ b/src/bescli/__main__.py @@ -4,7 +4,8 @@ import logging -from . import bescli +# from . import bescli +import bescli logging.basicConfig() bescli.main() diff --git a/src/bescli/bescli.py b/src/bescli/bescli.py index 68b8be5..e326f97 100644 --- a/src/bescli/bescli.py +++ b/src/bescli/bescli.py @@ -39,6 +39,13 @@ class BESCLInterface(Cmd): def __init__(self, **kwargs): Cmd.__init__(self, **kwargs) + + # set an intro message + self.intro = ( + f"\nWelcome to the BigFix REST API Interactive Python Module v{__version__}" + ) + + # sets the prompt look: self.prompt = "BigFix> " self.num_errors = 0 diff --git a/tests/tests.py b/tests/tests.py index 05d7e31..6b156c9 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -107,7 +107,9 @@ class RequestResult(object): # this should really only run if the config file is present: if bigfix_cli.bes_conn: # session relevance tests require functioning web reports server - print(bigfix_cli.bes_conn.session_relevance_string("number of bes computers")) + assert ( + int(bigfix_cli.bes_conn.session_relevance_string("number of bes computers")) > 0 + ) assert ( "test session relevance string result" in bigfix_cli.bes_conn.session_relevance_string( @@ -134,7 +136,7 @@ class RequestResult(object): var_name = "TestVarName" var_value = "TestVarValue " + str(random.randint(0, 9999)) - print( + assert var_value in str( bigfix_cli.bes_conn.set_dashboard_variable_value( dashboard_name, var_name, var_value ) From 63a260685e1e930979b878a8b8b7a329557c58c6 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Feb 2025 17:02:28 -0500 Subject: [PATCH 180/231] revert change that broke things --- src/bescli/__main__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bescli/__main__.py b/src/bescli/__main__.py index 7c831cb..ff6901a 100644 --- a/src/bescli/__main__.py +++ b/src/bescli/__main__.py @@ -4,8 +4,7 @@ import logging -# from . import bescli -import bescli +from . import bescli logging.basicConfig() bescli.main() From 60a5568b7f04c595d484c2a49e60fe67ad395ce1 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Feb 2025 17:09:48 -0500 Subject: [PATCH 181/231] add to automatic tests --- .github/workflows/test_build.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index 0b03faf..40299f3 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -33,12 +33,15 @@ jobs: python-version: ["3.9", "3"] steps: - uses: actions/checkout@v4 + - name: Set up Python uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Install build tools run: pip install setuptools wheel build pyinstaller + - name: Install requirements shell: bash run: if [ -f requirements.txt ]; then pip install -r requirements.txt; fi @@ -50,27 +53,45 @@ jobs: - name: Run Tests - Source run: python tests/tests.py + + - name: Test invoke directly src/bescli/bescli.py + run: python src/bescli/bescli.py ls logout clear error_count version exit + + - name: Test invoke directly src/besapi/besapi.py + run: python src/besapi/besapi.py ls logout clear error_count version exit + + - name: Test invoke directly src/besapi + run: python src/besapi ls logout clear error_count version exit + - name: Run build run: python3 -m build + - name: Get Wheel File Path id: getwheelfile shell: bash run: echo "::set-output name=wheelfile::$(find "dist" -type f -name "*.whl")" + - name: Test pip install of wheel shell: bash run: pip install $(find "dist" -type f -name "*.whl") + - name: Test python import besapi shell: bash run: python -c "import besapi" + - name: Test python import bescli shell: bash run: python -c "import bescli" + - name: Test python bescli shell: bash run: python -m bescli ls logout clear error_count version exit + - name: Run Tests - Pip run: python tests/tests.py --test_pip + - name: Test pyinstaller build run: pyinstaller --clean --collect-all besapi --onefile ./src/bescli/bescli.py + - name: Test bescli binary run: ./dist/bescli ls logout clear error_count version exit From e6b655e8753ef85dd4447e6b6f2ef9dd439025cc Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Feb 2025 17:13:05 -0500 Subject: [PATCH 182/231] tweak test_build --- .github/workflows/test_build.yaml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index 40299f3..e01930a 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -60,9 +60,6 @@ jobs: - name: Test invoke directly src/besapi/besapi.py run: python src/besapi/besapi.py ls logout clear error_count version exit - - name: Test invoke directly src/besapi - run: python src/besapi ls logout clear error_count version exit - - name: Run build run: python3 -m build @@ -95,3 +92,9 @@ jobs: - name: Test bescli binary run: ./dist/bescli ls logout clear error_count version exit + + - name: Test invoke directly -m besapi + run: cd src && python -m besapi ls logout clear error_count version exit + + - name: Test invoke directly -m bescli + run: cd src && python -m bescli ls logout clear error_count version exit From 7181b22814fb02d7a0b0b0cc21c55f3f54ba1a68 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Feb 2025 17:15:10 -0500 Subject: [PATCH 183/231] tweak test_build --- .github/workflows/test_build.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index e01930a..4cd4a66 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -60,6 +60,12 @@ jobs: - name: Test invoke directly src/besapi/besapi.py run: python src/besapi/besapi.py ls logout clear error_count version exit + - name: Test invoke directly -m besapi + run: cd src && python -m besapi ls logout clear error_count version exit + + - name: Test invoke directly -m bescli + run: cd src && python -m bescli ls logout clear error_count version exit + - name: Run build run: python3 -m build @@ -92,9 +98,3 @@ jobs: - name: Test bescli binary run: ./dist/bescli ls logout clear error_count version exit - - - name: Test invoke directly -m besapi - run: cd src && python -m besapi ls logout clear error_count version exit - - - name: Test invoke directly -m bescli - run: cd src && python -m bescli ls logout clear error_count version exit From 6d883929743c0b0e83c9edd337e86cdd53f74910 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Feb 2025 17:28:06 -0500 Subject: [PATCH 184/231] tweak test --- .github/workflows/test_build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index 4cd4a66..5086685 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -84,7 +84,7 @@ jobs: - name: Test python import bescli shell: bash - run: python -c "import bescli" + run: python -c "import bescli;bescli.bescli.BESCLInterface().do_version()" - name: Test python bescli shell: bash From ba3a1066b9ee43358af98163af305113369a211e Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Feb 2025 17:35:40 -0500 Subject: [PATCH 185/231] enhance test_build --- .github/workflows/test_build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_build.yaml b/.github/workflows/test_build.yaml index 5086685..41e5916 100644 --- a/.github/workflows/test_build.yaml +++ b/.github/workflows/test_build.yaml @@ -80,7 +80,7 @@ jobs: - name: Test python import besapi shell: bash - run: python -c "import besapi" + run: python -c "import besapi;print(besapi.besapi.__version__)" - name: Test python import bescli shell: bash From 1e226f093af1048a9f13da251b19037f7679c5c1 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 11 Feb 2025 17:37:47 -0500 Subject: [PATCH 186/231] minor update --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index d13127e..e8e3be4 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.7.6" +__version__ = "3.7.7" besapi_logger = logging.getLogger("besapi") From 4693d350eb267bdfbf173df2d7c1901bbc5c1ec2 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 12 Feb 2025 15:55:57 -0500 Subject: [PATCH 187/231] refactor bescli --- src/bescli/bescli.py | 92 +++++++++++++++++++++++++------------------- 1 file changed, 53 insertions(+), 39 deletions(-) diff --git a/src/bescli/bescli.py b/src/bescli/bescli.py index e326f97..2f4050c 100644 --- a/src/bescli/bescli.py +++ b/src/bescli/bescli.py @@ -10,6 +10,7 @@ """ import getpass +import json import logging import os import site @@ -80,13 +81,13 @@ def do_get(self, line): b = self.bes_conn.get(robjs[0]) # print objectify.ObjectPath(robjs[1:]) if b: - print(eval("b()." + ".".join(robjs[1:]))) + self.poutput(eval("b()." + ".".join(robjs[1:]))) else: output_item = self.bes_conn.get(line) - # print(type(output_item)) - print(output_item) - # print(output_item.besdict) - # print(output_item.besjson) + # self.poutput(type(output_item)) + self.poutput(output_item) + # self.poutput(output_item.besdict) + # self.poutput(output_item.besjson) else: self.pfeedback("Not currently logged in. Type 'login'.") @@ -104,16 +105,16 @@ def do_delete(self, line): if self.bes_conn: output_item = self.bes_conn.delete(line) - print(output_item) - # print(output_item.besdict) - # print(output_item.besjson) + self.poutput(output_item) + # self.poutput(output_item.besdict) + # self.poutput(output_item.besjson) else: self.pfeedback("Not currently logged in. Type 'login'.") def do_post(self, statement): """post file as data to path""" - print(statement) - print("not yet implemented") + self.poutput(statement) + self.poutput("not yet implemented") def do_config(self, conf_file=None): """Attempt to load config info from file and login""" @@ -244,7 +245,7 @@ def do_login(self, user=None): else: self.perror("Login Error!") - def do_logout(self, arg=None): + def do_logout(self, _=None): """Logout and clear session""" if self.bes_conn: self.bes_conn.logout() @@ -254,7 +255,7 @@ def do_logout(self, arg=None): def do_debug(self, setting): """Enable or Disable Debug Mode""" - print(bool(setting)) + self.poutput(bool(setting)) self.debug = bool(setting) self.echo = bool(setting) self.quiet = bool(setting) @@ -288,12 +289,12 @@ def do_saveconfig(self, arg=None): """save current config to file""" self.do_saveconf(arg) - def do_saveconf(self, arg=None): + def do_saveconf(self, _=None): """save current config to file""" if not self.bes_conn: self.do_login() if not self.bes_conn: - print("Can't save config without working login") + self.poutput("Can't save config without working login") else: conf_file_path = self.conf_path self.pfeedback(f"Saving Config File to: {conf_file_path}") @@ -304,29 +305,31 @@ def do_showconfig(self, arg=None): """List the current settings and connection status""" self.do_ls(arg) - def do_ls(self, arg=None): + def do_ls(self, _=None): """List the current settings and connection status""" - print(" Connected: " + str(bool(self.bes_conn))) - print( + self.poutput(" Connected: " + str(bool(self.bes_conn))) + self.poutput( " BES_ROOT_SERVER: " + (self.BES_ROOT_SERVER if self.BES_ROOT_SERVER else "") ) - print( + self.poutput( " BES_USER_NAME: " + (self.BES_USER_NAME if self.BES_USER_NAME else "") ) - print( + self.poutput( " Password Length: " + str(len(self.BES_PASSWORD if self.BES_PASSWORD else "")) ) - print(" Config File Path: " + self.conf_path) + self.poutput(" Config File Path: " + self.conf_path) if self.bes_conn: - print("Current Site Path: " + self.bes_conn.get_current_site_path(None)) + self.poutput( + "Current Site Path: " + self.bes_conn.get_current_site_path(None) + ) - def do_error_count(self, arg=None): + def do_error_count(self, _=None): """Output the number of errors""" self.poutput(f"Error Count: {self.num_errors}") - def do_exit(self, arg=None): + def do_exit(self, _=None): """Exit this application""" self.exit_code = self.num_errors # no matter what I try I can't get anything but exit code 0 on windows @@ -347,7 +350,7 @@ def do_query(self, statement): self.pfeedback("A: ") self.poutput(rel_result) - def do_version(self, statement=None): + def do_version(self, _=None): """output version of besapi""" self.poutput(f"besapi version: {__version__}") @@ -361,7 +364,7 @@ def do_get_operator(self, statement=None): result_op = self.bes_conn.get_user(statement) self.poutput(result_op) - def do_get_current_site(self, statement=None): + def do_get_current_site(self, _=None): """output current site path context""" self.poutput( f"Current Site Path: `{ self.bes_conn.get_current_site_path(None) }`" @@ -375,11 +378,11 @@ def do_set_current_site(self, statement=None): def do_get_content(self, resource_url): """get a specific item by resource url""" - print(self.bes_conn.get_content_by_resource(resource_url)) + self.poutput(self.bes_conn.get_content_by_resource(resource_url)) def do_export_item_by_resource(self, statement): """export content itemb to current folder""" - print(self.bes_conn.export_item_by_resource(statement)) + self.poutput(self.bes_conn.export_item_by_resource(statement)) def do_export_site(self, site_path): """export site contents to current folder""" @@ -387,7 +390,7 @@ def do_export_site(self, site_path): site_path, verbose=True, include_site_folder=False, include_item_ids=False ) - def do_export_all_sites(self, statement=None): + def do_export_all_sites(self, _=None): """export site contents to current folder""" self.bes_conn.export_all_sites(verbose=False) @@ -409,47 +412,58 @@ def do_import_bes(self, statement): def do_upload(self, file_path): """upload file to root server""" if not os.access(file_path, os.R_OK): - print(file_path, "is not a readable file") + self.poutput(file_path, "is not a readable file") else: upload_result = self.bes_conn.upload(file_path) - print(upload_result) - print(self.bes_conn.parse_upload_result_to_prefetch(upload_result)) + self.poutput(upload_result) + self.poutput(self.bes_conn.parse_upload_result_to_prefetch(upload_result)) complete_create_group = Cmd.path_complete def do_create_group(self, file_path): """create bigfix group from bes file""" if not os.access(file_path, os.R_OK): - print(file_path, "is not a readable file") + self.poutput(file_path, "is not a readable file") else: - print(self.bes_conn.create_group_from_file(file_path)) + self.poutput(self.bes_conn.create_group_from_file(file_path)) complete_create_user = Cmd.path_complete def do_create_user(self, file_path): """create bigfix user from bes file""" if not os.access(file_path, os.R_OK): - print(file_path, "is not a readable file") + self.poutput(file_path, "is not a readable file") else: - print(self.bes_conn.create_user_from_file(file_path)) + self.poutput(self.bes_conn.create_user_from_file(file_path)) complete_create_site = Cmd.path_complete def do_create_site(self, file_path): """create bigfix site from bes file""" if not os.access(file_path, os.R_OK): - print(file_path, "is not a readable file") + self.poutput(file_path, "is not a readable file") else: - print(self.bes_conn.create_site_from_file(file_path)) + self.poutput(self.bes_conn.create_site_from_file(file_path)) complete_update_item = Cmd.path_complete def do_update_item(self, file_path): """update bigfix content item from bes file""" if not os.access(file_path, os.R_OK): - print(file_path, "is not a readable file") + self.poutput(file_path, "is not a readable file") else: - print(self.bes_conn.update_item_from_file(file_path)) + self.poutput(self.bes_conn.update_item_from_file(file_path)) + + def do_serverinfo(self, _=None): + """get server info and return formatted""" + + # not sure what the minimum version for this is: + result = self.bes_conn.get("serverinfo") + + result_json = json.loads(result.text) + + self.poutput(f"\nServer Info for {self.BES_ROOT_SERVER}") + self.poutput(json.dumps(result_json, indent=2)) def main(): From d0d4751865aab922db1327090ff7c51d47be49a3 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 12 Feb 2025 16:46:14 -0500 Subject: [PATCH 188/231] add smart tab completion for get/delete/post --- src/bescli/bescli.py | 45 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/bescli/bescli.py b/src/bescli/bescli.py index 2f4050c..b21a410 100644 --- a/src/bescli/bescli.py +++ b/src/bescli/bescli.py @@ -57,8 +57,49 @@ def __init__(self, **kwargs): # set default config file path self.conf_path = os.path.expanduser("~/.besapi.conf") self.CONFPARSER = SafeConfigParser() + # for completion: + self.api_resources = [] self.do_conf() + def parse_help_resources(self): + """get api resources from help""" + if self.bes_conn: + help_result = self.bes_conn.get("help") + help_result = help_result.text.split("\n") + # print(help_result) + help_resources = [] + for item in help_result: + if "/api/" in item: + _, _, res = item.partition("/api/") + help_resources.append(res) + + return help_resources + else: + return [ + "actions", + "clientqueryresults", + "dashboardvariables", + "help", + "login", + "query", + "relaysites", + "serverinfo", + "sites", + ] + + def complete_api_resources(self, text, line, begidx, endidx): + """define completion for apis""" + + # only initialize once + if not self.api_resources: + self.api_resources = self.parse_help_resources() + + # TODO: make this work to complete only the first word after get/post/delete + # return the matching subset: + return [name for name in self.api_resources if name.startswith(text)] + + complete_get = complete_api_resources + def do_get(self, line): """Perform get request to BigFix server using provided api endpoint argument""" @@ -91,6 +132,8 @@ def do_get(self, line): else: self.pfeedback("Not currently logged in. Type 'login'.") + complete_delete = complete_api_resources + def do_delete(self, line): """Perform delete request to BigFix server using provided api endpoint argument""" @@ -111,6 +154,8 @@ def do_delete(self, line): else: self.pfeedback("Not currently logged in. Type 'login'.") + complete_post = complete_api_resources + def do_post(self, statement): """post file as data to path""" self.poutput(statement) From 95a36ce7fd25db48947d2376ba0ff91046c5ee3c Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 12 Feb 2025 16:48:54 -0500 Subject: [PATCH 189/231] strip whitespace just in case --- src/bescli/bescli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bescli/bescli.py b/src/bescli/bescli.py index b21a410..d15a4f6 100644 --- a/src/bescli/bescli.py +++ b/src/bescli/bescli.py @@ -71,7 +71,8 @@ def parse_help_resources(self): for item in help_result: if "/api/" in item: _, _, res = item.partition("/api/") - help_resources.append(res) + # strip whitespace just in case: + help_resources.append(res.strip()) return help_resources else: From 8ed5677bd6fefb5ff6b60b2d30b5831875566de4 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 12 Feb 2025 16:55:54 -0500 Subject: [PATCH 190/231] new release --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index e8e3be4..44cdfbc 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -29,7 +29,7 @@ from lxml import etree, objectify from pkg_resources import resource_filename -__version__ = "3.7.7" +__version__ = "3.7.8" besapi_logger = logging.getLogger("besapi") From c7ebed9809419e9146452c7c9ad51c4e66cedf6c Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 18 Feb 2025 12:22:02 -0500 Subject: [PATCH 191/231] add example for relay info --- examples/relay_info.py | 133 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 examples/relay_info.py diff --git a/examples/relay_info.py b/examples/relay_info.py new file mode 100644 index 0000000..a099bb1 --- /dev/null +++ b/examples/relay_info.py @@ -0,0 +1,133 @@ +""" +This will get info about relays in the environment + +requires `besapi`, install with command `pip install besapi` + +Example Usage: +python relay_info.py -r https://localhost:52311/api -u API_USER -p API_PASSWORD + +References: +- https://developer.bigfix.com/rest-api/api/admin.html +- https://github.com/jgstew/besapi/blob/master/examples/rest_cmd_args.py +- https://github.com/jgstew/tools/blob/master/Python/locate_self.py +""" + +import logging +import logging.handlers +import ntpath +import os +import platform +import shutil +import sys + +import besapi +import besapi.plugin_utilities + +__version__ = "1.1.1" +verbose = 0 +bes_conn = None +invoke_folder = None + + +def get_invoke_folder(verbose=0): + """Get the folder the script was invoked from""" + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_folder = os.path.abspath(os.path.dirname(sys.executable)) + else: + if verbose: + print("running in a normal Python process") + invoke_folder = os.path.abspath(os.path.dirname(__file__)) + + if verbose: + print(f"invoke_folder = {invoke_folder}") + + return invoke_folder + + +def get_invoke_file_name(verbose=0): + """Get the filename the script was invoked from""" + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_file_path = sys.executable + else: + if verbose: + print("running in a normal Python process") + invoke_file_path = __file__ + + if verbose: + print(f"invoke_file_path = {invoke_file_path}") + + # get just the file name, return without file extension: + return os.path.splitext(ntpath.basename(invoke_file_path))[0] + + +def main(): + """Execution starts here""" + print("main() start") + + print("NOTE: this script requires besapi v3.3.3+ due to besapi.plugin_utilities") + + parser = besapi.plugin_utilities.setup_plugin_argparse() + + # add additonal arg specific to this script: + parser.add_argument( + "-d", + "--days", + help="last report days to filter on", + required=False, + type=int, + default=900, + ) + # allow unknown args to be parsed instead of throwing an error: + args, _unknown = parser.parse_known_args() + + # allow set global scoped vars + global bes_conn, verbose, invoke_folder + verbose = args.verbose + + # get folder the script was invoked from: + invoke_folder = get_invoke_folder() + + log_file_path = os.path.join( + get_invoke_folder(verbose), get_invoke_file_name(verbose) + ".log" + ) + + print(log_file_path) + + logging_config = besapi.plugin_utilities.get_plugin_logging_config( + log_file_path, verbose, args.console + ) + + logging.basicConfig(**logging_config) + + logging.info("----- Starting New Session ------") + logging.debug("invoke folder: %s", invoke_folder) + logging.debug("Python version: %s", platform.sys.version) + logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) + logging.debug("this plugin's version: %s", __version__) + + bes_conn = besapi.plugin_utilities.get_besapi_connection(args) + + # get relay info: + last_report_days_filter = args.days + + session_relevance = f"""(multiplicity of it, it) of unique values of (it as string) of (relay selection method of it | "NoRelayMethod" , relay server of it | "NoRelayHostname") of bes computers whose(now - last report time of it < {last_report_days_filter} * day)""" + results = bes_conn.session_relevance_string(session_relevance) + + logging.info("Relay Info:\n" + results) + + session_relevance = f"""(multiplicity of it, it) of unique values of (it as string) of (relay selection method of it | "NoRelayMethod" , relay server of it | "NoRelayServer", relay hostname of it | "NoRelayHostname") of bes computers whose(now - last report time of it < {last_report_days_filter} * day AND relay server flag of it)""" + results = bes_conn.session_relevance_string(session_relevance) + + logging.info("Info on Relays:\n" + results) + + +if __name__ == "__main__": + main() From 349868d464779a14884e7f5b2b6ffa24e4648b4a Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 18 Feb 2025 12:23:53 -0500 Subject: [PATCH 192/231] add print statement note --- examples/relay_info.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/relay_info.py b/examples/relay_info.py index a099bb1..cdc4c0a 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -73,6 +73,9 @@ def main(): print("main() start") print("NOTE: this script requires besapi v3.3.3+ due to besapi.plugin_utilities") + print( + "WARNING: results may be incorrect if not run as a MO or an account without scope of all computers" + ) parser = besapi.plugin_utilities.setup_plugin_argparse() From 12daf37da69afbaf00de69267bb52a3c7afd155a Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 20 Feb 2025 11:34:13 -0500 Subject: [PATCH 193/231] add end session statement --- examples/relay_info.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/relay_info.py b/examples/relay_info.py index cdc4c0a..d4b9c81 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -110,7 +110,7 @@ def main(): logging.basicConfig(**logging_config) - logging.info("----- Starting New Session ------") + logging.info("---------- Starting New Session -----------") logging.debug("invoke folder: %s", invoke_folder) logging.debug("Python version: %s", platform.sys.version) logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) @@ -131,6 +131,8 @@ def main(): logging.info("Info on Relays:\n" + results) + logging.info("---------- Ending Session -----------") + if __name__ == "__main__": main() From adb946aed3d4e580d0944991123c4d05011017cc Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 20 Feb 2025 11:39:41 -0500 Subject: [PATCH 194/231] add relaynameoverride setting inspection --- examples/relay_info.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/relay_info.py b/examples/relay_info.py index d4b9c81..93ca0bb 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -131,6 +131,11 @@ def main(): logging.info("Info on Relays:\n" + results) + session_relevance = """unique values of values of client settings whose(name of it = "_BESClient_Relay_NameOverride") of bes computers""" + results = bes_conn.session_relevance_string(session_relevance) + + logging.info("Relay name override values:\n" + results) + logging.info("---------- Ending Session -----------") From 6e84ce3d714389ffd99c855014b9584ac1779a63 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 20 Feb 2025 11:41:47 -0500 Subject: [PATCH 195/231] add last report time filter --- examples/relay_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/relay_info.py b/examples/relay_info.py index 93ca0bb..034d087 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -131,7 +131,7 @@ def main(): logging.info("Info on Relays:\n" + results) - session_relevance = """unique values of values of client settings whose(name of it = "_BESClient_Relay_NameOverride") of bes computers""" + session_relevance = f"""unique values of values of client settings whose(name of it = "_BESClient_Relay_NameOverride") of bes computers whose(now - last report time of it < {last_report_days_filter} * day)""" results = bes_conn.session_relevance_string(session_relevance) logging.info("Relay name override values:\n" + results) From 638a319f53d2288d4f95b3062c0170c94cabc730 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 20 Feb 2025 11:42:23 -0500 Subject: [PATCH 196/231] tweak comments --- examples/relay_info.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/relay_info.py b/examples/relay_info.py index 034d087..f6d9084 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -118,9 +118,10 @@ def main(): bes_conn = besapi.plugin_utilities.get_besapi_connection(args) - # get relay info: + # defaults to 900 days: last_report_days_filter = args.days + # get relay info: session_relevance = f"""(multiplicity of it, it) of unique values of (it as string) of (relay selection method of it | "NoRelayMethod" , relay server of it | "NoRelayHostname") of bes computers whose(now - last report time of it < {last_report_days_filter} * day)""" results = bes_conn.session_relevance_string(session_relevance) From e849f7c32782d5dd46be08e8e14209e743e0cef5 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 20 Feb 2025 11:47:35 -0500 Subject: [PATCH 197/231] tweak example --- examples/relay_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/relay_info.py b/examples/relay_info.py index f6d9084..d582355 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -4,7 +4,7 @@ requires `besapi`, install with command `pip install besapi` Example Usage: -python relay_info.py -r https://localhost:52311/api -u API_USER -p API_PASSWORD +python relay_info.py -r https://localhost:52311/api -u API_USER --days 90 -p API_PASSWORD References: - https://developer.bigfix.com/rest-api/api/admin.html From 9359bcbb2dc0a0d79adb3b028fdec4c3f18b0eb6 Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 20 Feb 2025 14:17:13 -0500 Subject: [PATCH 198/231] fix logging method, get masthead parameters --- examples/relay_info.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/relay_info.py b/examples/relay_info.py index d582355..5218c49 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -12,6 +12,7 @@ - https://github.com/jgstew/tools/blob/master/Python/locate_self.py """ +import json import logging import logging.handlers import ntpath @@ -125,17 +126,24 @@ def main(): session_relevance = f"""(multiplicity of it, it) of unique values of (it as string) of (relay selection method of it | "NoRelayMethod" , relay server of it | "NoRelayHostname") of bes computers whose(now - last report time of it < {last_report_days_filter} * day)""" results = bes_conn.session_relevance_string(session_relevance) - logging.info("Relay Info:\n" + results) + logging.info("Relay Info:\n%s", results) session_relevance = f"""(multiplicity of it, it) of unique values of (it as string) of (relay selection method of it | "NoRelayMethod" , relay server of it | "NoRelayServer", relay hostname of it | "NoRelayHostname") of bes computers whose(now - last report time of it < {last_report_days_filter} * day AND relay server flag of it)""" results = bes_conn.session_relevance_string(session_relevance) - logging.info("Info on Relays:\n" + results) + logging.info("Info on Relays:\n%s", results) session_relevance = f"""unique values of values of client settings whose(name of it = "_BESClient_Relay_NameOverride") of bes computers whose(now - last report time of it < {last_report_days_filter} * day)""" results = bes_conn.session_relevance_string(session_relevance) - logging.info("Relay name override values:\n" + results) + logging.info("Relay name override values:\n%s", results) + + results = bes_conn.get("admin/masthead/parameters") + + logging.info( + "masthead parameters:\n%s", + json.dumps(results.besdict["MastheadParameters"], indent=2), + ) logging.info("---------- Ending Session -----------") From 1ea03535e4d8144f6deb1d5516c38a034e08204c Mon Sep 17 00:00:00 2001 From: JGStew Date: Thu, 20 Feb 2025 14:17:42 -0500 Subject: [PATCH 199/231] add comment --- examples/relay_info.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/relay_info.py b/examples/relay_info.py index 5218c49..06bfcc1 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -138,6 +138,7 @@ def main(): logging.info("Relay name override values:\n%s", results) + # this should require MO: results = bes_conn.get("admin/masthead/parameters") logging.info( From c3e4199fd37397828164899272f5a4498403e062 Mon Sep 17 00:00:00 2001 From: JGStew Date: Fri, 21 Feb 2025 10:16:44 -0500 Subject: [PATCH 200/231] add more info checks --- examples/relay_info.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/examples/relay_info.py b/examples/relay_info.py index 06bfcc1..a5cb7d2 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -146,6 +146,28 @@ def main(): json.dumps(results.besdict["MastheadParameters"], indent=2), ) + # this should require MO: + results = bes_conn.get("admin/fields") + + logging.info( + "Admin Fields:\n%s", json.dumps(results.besdict["AdminField"], indent=2) + ) + + # this should require MO: + results = bes_conn.get("admin/options") + + logging.info( + "Admin Options:\n%s", json.dumps(results.besdict["SystemOptions"], indent=2) + ) + + # this should require MO: + results = bes_conn.get("admin/reports") + + logging.info( + "Admin Report Options:\n%s", + json.dumps(results.besdict["ClientReports"], indent=2), + ) + logging.info("---------- Ending Session -----------") From 66d4771ea6f8d97b7b2bec14db2149d9efd32840 Mon Sep 17 00:00:00 2001 From: JGStew Date: Fri, 21 Feb 2025 13:35:04 -0500 Subject: [PATCH 201/231] switch example to use plugin or config file for besapi creds --- examples/client_query_from_string.py | 95 +++++++++++++++++++++++++++- 1 file changed, 92 insertions(+), 3 deletions(-) diff --git a/examples/client_query_from_string.py b/examples/client_query_from_string.py index 0298043..5d489f9 100644 --- a/examples/client_query_from_string.py +++ b/examples/client_query_from_string.py @@ -5,19 +5,108 @@ """ import json +import logging +import ntpath +import os +import platform import sys import time import besapi +import besapi.plugin_utilities CLIENT_RELEVANCE = "(computer names, model name of main processor, (it as string) of (it / (1024 * 1024 * 1024)) of total amount of ram)" +__version__ = "1.0.1" +verbose = 0 +bes_conn = None +invoke_folder = None + + +def get_invoke_folder(verbose=0): + """Get the folder the script was invoked from""" + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_folder = os.path.abspath(os.path.dirname(sys.executable)) + else: + if verbose: + print("running in a normal Python process") + invoke_folder = os.path.abspath(os.path.dirname(__file__)) + + if verbose: + print(f"invoke_folder = {invoke_folder}") + + return invoke_folder + + +def get_invoke_file_name(verbose=0): + """Get the filename the script was invoked from""" + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_file_path = sys.executable + else: + if verbose: + print("running in a normal Python process") + invoke_file_path = __file__ + + if verbose: + print(f"invoke_file_path = {invoke_file_path}") + + # get just the file name, return without file extension: + return os.path.splitext(ntpath.basename(invoke_file_path))[0] def main(): """Execution starts here""" print("main()") - bes_conn = besapi.besapi.get_bes_conn_using_config_file() - bes_conn.login() + + print("NOTE: this script requires besapi v3.3.3+ due to besapi.plugin_utilities") + + parser = besapi.plugin_utilities.setup_plugin_argparse() + + # add additonal arg specific to this script: + parser.add_argument( + "-q", + "--query", + help="client query relevance", + required=False, + type=str, + default=CLIENT_RELEVANCE, + ) + # allow unknown args to be parsed instead of throwing an error: + args, _unknown = parser.parse_known_args() + + # allow set global scoped vars + global bes_conn, verbose, invoke_folder + verbose = args.verbose + + # get folder the script was invoked from: + invoke_folder = get_invoke_folder() + + log_file_path = os.path.join( + get_invoke_folder(verbose), get_invoke_file_name(verbose) + ".log" + ) + + print(log_file_path) + + logging_config = besapi.plugin_utilities.get_plugin_logging_config( + log_file_path, verbose, args.console + ) + + logging.basicConfig(**logging_config) + + logging.info("---------- Starting New Session -----------") + logging.debug("invoke folder: %s", invoke_folder) + logging.debug("Python version: %s", platform.sys.version) + logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) + logging.debug("this plugin's version: %s", __version__) + + bes_conn = besapi.plugin_utilities.get_besapi_connection(args) # get the ~10 most recent computers to report into BigFix: session_relevance = 'tuple string items (integers in (0,9)) of concatenations ", " of (it as string) of ids of bes computers whose(now - last report time of it < 25 * minute)' @@ -36,7 +125,7 @@ def main(): # print(item) # this is the client relevance we are going to get the results of: - client_relevance = CLIENT_RELEVANCE + client_relevance = args.query # generate target XML substring from list of computer ids: target_xml = ( From 65c7a1c70bd7b5075718bf45f672208b3b6fde17 Mon Sep 17 00:00:00 2001 From: JGStew Date: Mon, 24 Feb 2025 14:23:35 -0500 Subject: [PATCH 202/231] first step for action monitor --- examples/action_and_monitor.py | 233 +++++++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 examples/action_and_monitor.py diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py new file mode 100644 index 0000000..e2d8db1 --- /dev/null +++ b/examples/action_and_monitor.py @@ -0,0 +1,233 @@ +""" +Create an action and monitor it's results for ~120 seconds + +requires `besapi`, install with command `pip install besapi` +""" + +import logging +import ntpath +import os +import platform +import sys +import time + +import lxml.etree + +import besapi +import besapi.plugin_utilities + +__version__ = "1.0.1" +verbose = 0 +bes_conn = None +invoke_folder = None + + +def get_invoke_folder(verbose=0): + """Get the folder the script was invoked from""" + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_folder = os.path.abspath(os.path.dirname(sys.executable)) + else: + if verbose: + print("running in a normal Python process") + invoke_folder = os.path.abspath(os.path.dirname(__file__)) + + if verbose: + print(f"invoke_folder = {invoke_folder}") + + return invoke_folder + + +def get_invoke_file_name(verbose=0): + """Get the filename the script was invoked from""" + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_file_path = sys.executable + else: + if verbose: + print("running in a normal Python process") + invoke_file_path = __file__ + + if verbose: + print(f"invoke_file_path = {invoke_file_path}") + + # get just the file name, return without file extension: + return os.path.splitext(ntpath.basename(invoke_file_path))[0] + + +def get_action_relevance(relevances): + """take array of ordered relevance clauses and return relevance string for action""" + + relevance = "" + + if not relevances: + return "False" + if len(relevances) == 0: + return "False" + if len(relevances) == 1: + return relevances[0] + if len(relevances) > 0: + for clause in relevances: + if len(relevance) == 0: + relevance = clause + else: + relevance = "( " + relevance + " ) AND ( " + clause + " )" + + return relevance + + +def main(): + """Execution starts here""" + print("main()") + + print("NOTE: this script requires besapi v3.3.3+ due to besapi.plugin_utilities") + + parser = besapi.plugin_utilities.setup_plugin_argparse() + + # add additonal arg specific to this script: + parser.add_argument( + "-f", + "--file", + help="xml bes file to create an action from", + required=False, + type=str, + ) + # allow unknown args to be parsed instead of throwing an error: + args, _unknown = parser.parse_known_args() + + # allow set global scoped vars + global bes_conn, verbose, invoke_folder + verbose = args.verbose + + # get folder the script was invoked from: + invoke_folder = get_invoke_folder() + + log_file_path = os.path.join( + get_invoke_folder(verbose), get_invoke_file_name(verbose) + ".log" + ) + + print(log_file_path) + + logging_config = besapi.plugin_utilities.get_plugin_logging_config( + log_file_path, verbose, args.console + ) + + logging.basicConfig(**logging_config) + + logging.info("---------- Starting New Session -----------") + logging.debug("invoke folder: %s", invoke_folder) + logging.debug("Python version: %s", platform.sys.version) + logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) + logging.debug("this plugin's version: %s", __version__) + + bes_conn = besapi.plugin_utilities.get_besapi_connection(args) + + tree = lxml.etree.parse(args.file) + title = tree.xpath("//BES/*[self::Task or self::Fixlet]/Title/text()")[0] + + logging.debug("Title: %s", title) + + actionscript = tree.xpath( + "//BES/*[self::Task or self::Fixlet]/DefaultAction/ActionScript/text()" + )[0] + + logging.debug("ActionScript: %s", actionscript) + + success_criteria = tree.xpath( + "//BES/*[self::Task or self::Fixlet]/DefaultAction/SuccessCriteria/@Option" + )[0] + + logging.debug("success_criteria: %s", success_criteria) + + relevance_clauses = tree.xpath( + "//BES/*[self::Task or self::Fixlet]/Relevance/text()" + ) + + logging.debug("Relevances: %s", relevance_clauses) + + relevance_clauses_combined = get_action_relevance(relevance_clauses) + + logging.debug("Relevance Combined: %s", relevance_clauses_combined) + + action_xml = f""" + + + {title} + + + + + false + + + +""" + + logging.debug("Action XML:\n%s", action_xml) + + action_result = bes_conn.post(bes_conn.url("actions"), data=action_xml) + + logging.debug("Action Result:/n%s", action_result) + + logging.debug("Action ID: %s", action_result.besobj.Action.ID) + + logging.info("work in progress! Need to monitor results of action!") + + # work in progress, haulting here: + return None + # TODO: do the stuff: + + # print(query_payload) + + # send the client query: (need it's ID to get results) + query_submit_result = bes_conn.post(bes_conn.url("clientquery"), data=query_payload) + + # print(query_submit_result) + # print(query_submit_result.besobj.ClientQuery.ID) + + previous_result = "" + i = 0 + try: + # loop ~120 second for results + while i < 12: + print("... waiting for results ... Ctrl+C to quit loop") + + # TODO: loop this to keep getting more results until all return or any key pressed + time.sleep(10) + + # get the actual results: + # NOTE: this might not return anything if no clients have returned results + # this can be checked again and again for more results: + query_result = bes_conn.get( + bes_conn.url( + f"clientqueryresults/{query_submit_result.besobj.ClientQuery.ID}" + ) + ) + + if previous_result != str(query_result): + print(query_result) + previous_result = str(query_result) + + i += 1 + + # if not running interactively: + # https://stackoverflow.com/questions/2356399/tell-if-python-is-in-interactive-mode + if not sys.__stdin__.isatty(): + print("not interactive, stopping loop") + break + except KeyboardInterrupt: + print("\nloop interuppted") + + print("script finished") + + +if __name__ == "__main__": + main() From 5e5b39ada1b574eeccbf0b35e2e955077e6ca311 Mon Sep 17 00:00:00 2001 From: JGStew Date: Mon, 24 Feb 2025 15:14:02 -0500 Subject: [PATCH 203/231] monitoring is working --- examples/action_and_monitor.py | 46 +++++++++++++++------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index e2d8db1..dd9b966 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -61,7 +61,7 @@ def get_invoke_file_name(verbose=0): return os.path.splitext(ntpath.basename(invoke_file_path))[0] -def get_action_relevance(relevances): +def get_action_combined_relevance(relevances): """take array of ordered relevance clauses and return relevance string for action""" relevance = "" @@ -151,7 +151,7 @@ def main(): logging.debug("Relevances: %s", relevance_clauses) - relevance_clauses_combined = get_action_relevance(relevance_clauses) + relevance_clauses_combined = get_action_combined_relevance(relevance_clauses) logging.debug("Relevance Combined: %s", relevance_clauses_combined) @@ -165,7 +165,7 @@ def main(): // End]]> - false + true @@ -177,56 +177,50 @@ def main(): logging.debug("Action Result:/n%s", action_result) - logging.debug("Action ID: %s", action_result.besobj.Action.ID) + action_id = action_result.besobj.Action.ID - logging.info("work in progress! Need to monitor results of action!") + logging.debug("Action ID: %s", action_id) - # work in progress, haulting here: - return None - # TODO: do the stuff: - - # print(query_payload) - - # send the client query: (need it's ID to get results) - query_submit_result = bes_conn.post(bes_conn.url("clientquery"), data=query_payload) - - # print(query_submit_result) - # print(query_submit_result.besobj.ClientQuery.ID) + logging.info("Monitoring action results:") previous_result = "" i = 0 try: - # loop ~120 second for results - while i < 12: + # loop ~300 second for results + while i < 30: print("... waiting for results ... Ctrl+C to quit loop") - # TODO: loop this to keep getting more results until all return or any key pressed time.sleep(10) # get the actual results: + # api/action/ACTION_ID/status?fields=ActionID,Status,DateIssued,DateStopped,StoppedBy,Computer(Status,State,StartTime) # NOTE: this might not return anything if no clients have returned results # this can be checked again and again for more results: - query_result = bes_conn.get( + action_status_result = bes_conn.get( bes_conn.url( - f"clientqueryresults/{query_submit_result.besobj.ClientQuery.ID}" + f"action/{action_id}/status?fields=ActionID,Status,DateIssued,DateStopped,StoppedBy,Computer(Status,State,StartTime)" ) ) - if previous_result != str(query_result): - print(query_result) - previous_result = str(query_result) + if previous_result != str(action_status_result): + logging.info(action_status_result) + previous_result = str(action_status_result) i += 1 + if action_status_result.besobj.ActionResults.Status == "Stopped": + logging.info("Action is stopped, halting monitoring loop") + break + # if not running interactively: # https://stackoverflow.com/questions/2356399/tell-if-python-is-in-interactive-mode if not sys.__stdin__.isatty(): - print("not interactive, stopping loop") + logging.warning("not interactive, stopping loop") break except KeyboardInterrupt: print("\nloop interuppted") - print("script finished") + logging.info("---------- END -----------") if __name__ == "__main__": From 1716a34d245a10df59caacc25c8ca8d76f70d2b1 Mon Sep 17 00:00:00 2001 From: JGStew Date: Mon, 24 Feb 2025 15:48:10 -0500 Subject: [PATCH 204/231] improve comments and logging --- examples/action_and_monitor.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index dd9b966..6b11832 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -1,5 +1,5 @@ """ -Create an action and monitor it's results for ~120 seconds +Create an action from fixlet/task xml bes file and monitor it's results for ~300 seconds requires `besapi`, install with command `pip install besapi` """ @@ -175,11 +175,11 @@ def main(): action_result = bes_conn.post(bes_conn.url("actions"), data=action_xml) - logging.debug("Action Result:/n%s", action_result) + logging.info("Action Result:/n%s", action_result) action_id = action_result.besobj.Action.ID - logging.debug("Action ID: %s", action_id) + logging.info("Action ID: %s", action_id) logging.info("Monitoring action results:") From 6377a69776213d9eccfdf9531e848bddd59a822e Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 09:15:17 -0500 Subject: [PATCH 205/231] handle missing success criteria --- examples/action_and_monitor.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 6b11832..15aade0 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -139,9 +139,18 @@ def main(): logging.debug("ActionScript: %s", actionscript) - success_criteria = tree.xpath( - "//BES/*[self::Task or self::Fixlet]/DefaultAction/SuccessCriteria/@Option" - )[0] + try: + success_criteria = tree.xpath( + "//BES/*[self::Task or self::Fixlet]/DefaultAction/SuccessCriteria/@Option" + )[0] + except IndexError: + # TODO: check if task or fixlet first? + success_criteria = "RunToCompletion" + + if success_criteria == "CustomRelevance": + # TODO: add handling for CustomRelevance case? + logging.error("SuccessCriteria = %s is not handled!", success_criteria) + sys.exit(1) logging.debug("success_criteria: %s", success_criteria) From 6864da845c9e747b05690197302a96f921f78201 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 12:07:42 -0500 Subject: [PATCH 206/231] add use case for custom relevance success criteria, simplify use of getting the type. --- examples/action_and_monitor.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 15aade0..4d32440 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -128,35 +128,41 @@ def main(): bes_conn = besapi.plugin_utilities.get_besapi_connection(args) + # default to empty string: + custom_relevance_xml = "" + tree = lxml.etree.parse(args.file) - title = tree.xpath("//BES/*[self::Task or self::Fixlet]/Title/text()")[0] + + # //BES/*[self::Task or self::Fixlet]/*[@id='elid']/name() + bes_type = str(tree.xpath("//BES/*[self::Task or self::Fixlet]")[0].tag) + + logging.debug("BES Type: %s", bes_type) + + title = tree.xpath(f"//BES/{bes_type}/Title/text()")[0] logging.debug("Title: %s", title) - actionscript = tree.xpath( - "//BES/*[self::Task or self::Fixlet]/DefaultAction/ActionScript/text()" - )[0] + actionscript = tree.xpath(f"//BES/{bes_type}/DefaultAction/ActionScript/text()")[0] logging.debug("ActionScript: %s", actionscript) try: success_criteria = tree.xpath( - "//BES/*[self::Task or self::Fixlet]/DefaultAction/SuccessCriteria/@Option" + f"//BES/{bes_type}/DefaultAction/SuccessCriteria/@Option" )[0] except IndexError: # TODO: check if task or fixlet first? success_criteria = "RunToCompletion" if success_criteria == "CustomRelevance": - # TODO: add handling for CustomRelevance case? - logging.error("SuccessCriteria = %s is not handled!", success_criteria) - sys.exit(1) + custom_relevance = tree.xpath( + f"//BES/{bes_type}/DefaultAction/SuccessCriteria/text()" + )[0] + custom_relevance_xml = f"" logging.debug("success_criteria: %s", success_criteria) - relevance_clauses = tree.xpath( - "//BES/*[self::Task or self::Fixlet]/Relevance/text()" - ) + relevance_clauses = tree.xpath(f"//BES/{bes_type}/Relevance/text()") logging.debug("Relevances: %s", relevance_clauses) @@ -172,7 +178,7 @@ def main(): - + {custom_relevance_xml} true From 54787b785a45ba9211066c0532d47028c2f72538 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 12:28:49 -0500 Subject: [PATCH 207/231] handle fixlet success criteria better --- examples/action_and_monitor.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 4d32440..62d1cbc 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -151,8 +151,11 @@ def main(): f"//BES/{bes_type}/DefaultAction/SuccessCriteria/@Option" )[0] except IndexError: - # TODO: check if task or fixlet first? + # set success criteria if missing: (default) success_criteria = "RunToCompletion" + if bes_type == "Fixlet": + # set success criteria if missing: (Fixlet) + success_criteria = "OriginalRelevance" if success_criteria == "CustomRelevance": custom_relevance = tree.xpath( @@ -205,7 +208,7 @@ def main(): while i < 30: print("... waiting for results ... Ctrl+C to quit loop") - time.sleep(10) + time.sleep(15) # get the actual results: # api/action/ACTION_ID/status?fields=ActionID,Status,DateIssued,DateStopped,StoppedBy,Computer(Status,State,StartTime) From 84c0a642a68017d650a94cafa01d960163b8227b Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 12:41:46 -0500 Subject: [PATCH 208/231] refactor action_and_monitor into function --- examples/action_and_monitor.py | 120 ++++++++++++++++++--------------- 1 file changed, 67 insertions(+), 53 deletions(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 62d1cbc..d40cda3 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -1,7 +1,10 @@ """ -Create an action from fixlet/task xml bes file and monitor it's results for ~300 seconds +Create an action from a fixlet or task xml bes file +and monitor it's results for ~300 seconds requires `besapi`, install with command `pip install besapi` + +NOTE: this script requires besapi v3.3.3+ due to use of besapi.plugin_utilities """ import logging @@ -64,7 +67,7 @@ def get_invoke_file_name(verbose=0): def get_action_combined_relevance(relevances): """take array of ordered relevance clauses and return relevance string for action""" - relevance = "" + relevance_combined = "" if not relevances: return "False" @@ -74,64 +77,24 @@ def get_action_combined_relevance(relevances): return relevances[0] if len(relevances) > 0: for clause in relevances: - if len(relevance) == 0: - relevance = clause + if len(relevance_combined) == 0: + relevance_combined = clause else: - relevance = "( " + relevance + " ) AND ( " + clause + " )" - - return relevance - - -def main(): - """Execution starts here""" - print("main()") - - print("NOTE: this script requires besapi v3.3.3+ due to besapi.plugin_utilities") - - parser = besapi.plugin_utilities.setup_plugin_argparse() - - # add additonal arg specific to this script: - parser.add_argument( - "-f", - "--file", - help="xml bes file to create an action from", - required=False, - type=str, - ) - # allow unknown args to be parsed instead of throwing an error: - args, _unknown = parser.parse_known_args() - - # allow set global scoped vars - global bes_conn, verbose, invoke_folder - verbose = args.verbose - - # get folder the script was invoked from: - invoke_folder = get_invoke_folder() - - log_file_path = os.path.join( - get_invoke_folder(verbose), get_invoke_file_name(verbose) + ".log" - ) - - print(log_file_path) - - logging_config = besapi.plugin_utilities.get_plugin_logging_config( - log_file_path, verbose, args.console - ) - - logging.basicConfig(**logging_config) + relevance_combined = ( + "( " + relevance_combined + " ) AND ( " + clause + " )" + ) - logging.info("---------- Starting New Session -----------") - logging.debug("invoke folder: %s", invoke_folder) - logging.debug("Python version: %s", platform.sys.version) - logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) - logging.debug("this plugin's version: %s", __version__) + return relevance_combined - bes_conn = besapi.plugin_utilities.get_besapi_connection(args) +def action_and_monitor(bes_conn, file_path): + """Take action from bes xml file + monitor results of action""" + # TODO: allow input variable for custom targeting # default to empty string: custom_relevance_xml = "" - tree = lxml.etree.parse(args.file) + tree = lxml.etree.parse(file_path) # //BES/*[self::Task or self::Fixlet]/*[@id='elid']/name() bes_type = str(tree.xpath("//BES/*[self::Task or self::Fixlet]")[0].tag) @@ -238,6 +201,57 @@ def main(): except KeyboardInterrupt: print("\nloop interuppted") + return previous_result + + +def main(): + """Execution starts here""" + print("main()") + + print("NOTE: this script requires besapi v3.3.3+ due to besapi.plugin_utilities") + + parser = besapi.plugin_utilities.setup_plugin_argparse() + + # add additonal arg specific to this script: + parser.add_argument( + "-f", + "--file", + help="xml bes file to create an action from", + required=False, + type=str, + ) + # allow unknown args to be parsed instead of throwing an error: + args, _unknown = parser.parse_known_args() + + # allow set global scoped vars + global bes_conn, verbose, invoke_folder + verbose = args.verbose + + # get folder the script was invoked from: + invoke_folder = get_invoke_folder() + + log_file_path = os.path.join( + get_invoke_folder(verbose), get_invoke_file_name(verbose) + ".log" + ) + + print(log_file_path) + + logging_config = besapi.plugin_utilities.get_plugin_logging_config( + log_file_path, verbose, args.console + ) + + logging.basicConfig(**logging_config) + + logging.info("---------- Starting New Session -----------") + logging.debug("invoke folder: %s", invoke_folder) + logging.debug("Python version: %s", platform.sys.version) + logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) + logging.debug("this plugin's version: %s", __version__) + + bes_conn = besapi.plugin_utilities.get_besapi_connection(args) + + action_and_monitor(bes_conn, args.file) + logging.info("---------- END -----------") From 2daa190d48c3a3cafcfe892b1f12184ce2ac73b8 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 12:43:29 -0500 Subject: [PATCH 209/231] add example content --- examples/content/Test Echo - Universal.bes | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 examples/content/Test Echo - Universal.bes diff --git a/examples/content/Test Echo - Universal.bes b/examples/content/Test Echo - Universal.bes new file mode 100644 index 0000000..03cae8c --- /dev/null +++ b/examples/content/Test Echo - Universal.bes @@ -0,0 +1,30 @@ + + + + test echo - all + + + + Internal + + 2021-08-03 + + + + + x-fixlet-modification-time + Tue, 03 Aug 2021 15:18:27 +0000 + + BESC + + + Click + here + to deploy this action. + + {(concatenations (if windows of operating system then "^ " else "\ ") of substrings separated by " " of it) of pathname of folders "Logs" of folders "__Global" of data folders of client}{if windows of operating system then "\" else "/"}test_echo.log" +]]> + + + From c6f202c60cfa44d6246d33033da7ee401a9bbbb2 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 12:59:34 -0500 Subject: [PATCH 210/231] refactoring into functions action_and_monitor --- examples/action_and_monitor.py | 32 ++++++++++++++++------ examples/content/Test Echo - Universal.bes | 4 +-- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index d40cda3..82dbd92 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -19,9 +19,8 @@ import besapi import besapi.plugin_utilities -__version__ = "1.0.1" +__version__ = "1.2.1" verbose = 0 -bes_conn = None invoke_folder = None @@ -87,9 +86,8 @@ def get_action_combined_relevance(relevances): return relevance_combined -def action_and_monitor(bes_conn, file_path): - """Take action from bes xml file - monitor results of action""" +def action_from_bes_file(bes_conn, file_path): + """create action from bes file with fixlet or task""" # TODO: allow input variable for custom targeting # default to empty string: custom_relevance_xml = "" @@ -162,16 +160,19 @@ def action_and_monitor(bes_conn, file_path): logging.info("Action ID: %s", action_id) - logging.info("Monitoring action results:") + return action_id + +def action_monitor_results(bes_conn, action_id, iterations=30, sleep_time=15): + """monitor the results of an action if interactive""" previous_result = "" i = 0 try: # loop ~300 second for results - while i < 30: + while i < iterations: print("... waiting for results ... Ctrl+C to quit loop") - time.sleep(15) + time.sleep(sleep_time) # get the actual results: # api/action/ACTION_ID/status?fields=ActionID,Status,DateIssued,DateStopped,StoppedBy,Computer(Status,State,StartTime) @@ -204,6 +205,19 @@ def action_and_monitor(bes_conn, file_path): return previous_result +def action_and_monitor(bes_conn, file_path): + """Take action from bes xml file + monitor results of action""" + + action_id = action_from_bes_file(bes_conn, file_path) + + logging.info("Start monitoring action results:") + + results_action = action_monitor_results(bes_conn, action_id) + + logging.info("End monitoring, Last Result:\n%s", results_action) + + def main(): """Execution starts here""" print("main()") @@ -224,7 +238,7 @@ def main(): args, _unknown = parser.parse_known_args() # allow set global scoped vars - global bes_conn, verbose, invoke_folder + global verbose, invoke_folder verbose = args.verbose # get folder the script was invoked from: diff --git a/examples/content/Test Echo - Universal.bes b/examples/content/Test Echo - Universal.bes index 03cae8c..21e94a1 100644 --- a/examples/content/Test Echo - Universal.bes +++ b/examples/content/Test Echo - Universal.bes @@ -3,7 +3,7 @@ test echo - all - + Internal @@ -23,7 +23,7 @@ to deploy this action.
{(concatenations (if windows of operating system then "^ " else "\ ") of substrings separated by " " of it) of pathname of folders "Logs" of folders "__Global" of data folders of client}{if windows of operating system then "\" else "/"}test_echo.log" +wait {if windows of operating system then "CMD /C" else "bash -c"} "echo "test12345678" > {(concatenations (if windows of operating system then "^ " else "\ ") of substrings separated by " " of it) of pathname of folders "Logs" of folders "__Global" of data folders of client}{if windows of operating system then "\" else "/"}test_echo.log" ]]> From 9b6c2b4d9fd5fe62e2b4c60efda2108c5afc3aa2 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 13:06:27 -0500 Subject: [PATCH 211/231] add example usage comment --- examples/action_and_monitor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 82dbd92..f0b6207 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -5,6 +5,9 @@ requires `besapi`, install with command `pip install besapi` NOTE: this script requires besapi v3.3.3+ due to use of besapi.plugin_utilities + +Example Usage: +python3 examples/action_and_monitor.py -c -vv --file './examples/content/Test Echo - Universal.bes' """ import logging From af9186532b6101ca5236cb3cc9c4ceb25a333118 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 13:12:54 -0500 Subject: [PATCH 212/231] tweak logging info order --- examples/action_and_monitor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index f0b6207..54bdfb7 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -261,9 +261,9 @@ def main(): logging.info("---------- Starting New Session -----------") logging.debug("invoke folder: %s", invoke_folder) - logging.debug("Python version: %s", platform.sys.version) + logging.debug("%s's version: %s", get_invoke_file_name(verbose), __version__) logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) - logging.debug("this plugin's version: %s", __version__) + logging.debug("Python version: %s", platform.sys.version) bes_conn = besapi.plugin_utilities.get_besapi_connection(args) From 608340d1d0b4c9fccd7fe8830e44c48b817ef7b4 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 13:18:24 -0500 Subject: [PATCH 213/231] add typing for function input --- examples/action_and_monitor.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 54bdfb7..2d7ed1d 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -16,6 +16,7 @@ import platform import sys import time +import typing import lxml.etree @@ -66,7 +67,7 @@ def get_invoke_file_name(verbose=0): return os.path.splitext(ntpath.basename(invoke_file_path))[0] -def get_action_combined_relevance(relevances): +def get_action_combined_relevance(relevances: typing.List[str]): """take array of ordered relevance clauses and return relevance string for action""" relevance_combined = "" From ab85ea956dd81c14f0a959029a00ad73ebfc4a7a Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 14:10:10 -0500 Subject: [PATCH 214/231] handle target xml --- examples/action_and_monitor.py | 59 +++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 2d7ed1d..1d3cc88 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -90,7 +90,58 @@ def get_action_combined_relevance(relevances: typing.List[str]): return relevance_combined -def action_from_bes_file(bes_conn, file_path): +def get_target_xml(targets=""): + """get target xml based upon input + + Input can be a single string: + - starts with "" if all computers should be targeted + - Otherwise will be interpreted as custom relevance + + Input can be a single int: + - Single Computer ID Target + + Input can be an array: + - Array of Strings: ComputerName + - Array of Integers: ComputerID + """ + + # if targets is int: + if isinstance(targets, int): + return f"{targets}" + + # if targets is str: + if isinstance(targets, str): + # if targets string starts with "": + if targets.startswith(""): + return "true" + # treat as custom relevance: + return f"" + + # if targets is array: + if isinstance(targets, list): + element_type = type(targets[0]) + if element_type is int: + # array of computer ids + return ( + "" + + "".join(map(str, targets)) + + "" + ) + if element_type is str: + # array of computer names + return ( + "" + + "".join(targets) + + "" + ) + + logging.warning("No valid targeting found, will target no computers.") + + # default if invalid: + return "False" + + +def action_from_bes_file(bes_conn, file_path, targets=""): """create action from bes file with fixlet or task""" # TODO: allow input variable for custom targeting # default to empty string: @@ -148,7 +199,7 @@ def action_from_bes_file(bes_conn, file_path): // End]]> {custom_relevance_xml} - true + { get_target_xml(targets) }
@@ -209,11 +260,11 @@ def action_monitor_results(bes_conn, action_id, iterations=30, sleep_time=15): return previous_result -def action_and_monitor(bes_conn, file_path): +def action_and_monitor(bes_conn, file_path, targets=""): """Take action from bes xml file monitor results of action""" - action_id = action_from_bes_file(bes_conn, file_path) + action_id = action_from_bes_file(bes_conn, file_path, targets) logging.info("Start monitoring action results:") From 392fe20f11f4ca60292a52df91fa82ace2eb7d3c Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 14:17:37 -0500 Subject: [PATCH 215/231] add relay computer id to output --- examples/relay_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/relay_info.py b/examples/relay_info.py index a5cb7d2..a8e6089 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -123,12 +123,12 @@ def main(): last_report_days_filter = args.days # get relay info: - session_relevance = f"""(multiplicity of it, it) of unique values of (it as string) of (relay selection method of it | "NoRelayMethod" , relay server of it | "NoRelayHostname") of bes computers whose(now - last report time of it < {last_report_days_filter} * day)""" + session_relevance = f"""(multiplicity of it, it) of unique values of (it as string) of (relay selection method of it | "NoRelayMethod" , relay server of it | "NoRelayServer") of bes computers whose(now - last report time of it < {last_report_days_filter} * day)""" results = bes_conn.session_relevance_string(session_relevance) logging.info("Relay Info:\n%s", results) - session_relevance = f"""(multiplicity of it, it) of unique values of (it as string) of (relay selection method of it | "NoRelayMethod" , relay server of it | "NoRelayServer", relay hostname of it | "NoRelayHostname") of bes computers whose(now - last report time of it < {last_report_days_filter} * day AND relay server flag of it)""" + session_relevance = f"""(multiplicity of it, it) of unique values of (it as string) of (relay selection method of it | "NoRelayMethod" , relay server of it | "NoRelayServer", relay hostname of it | "NoRelayHostname", id of it | 0) of bes computers whose(now - last report time of it < {last_report_days_filter} * day AND relay server flag of it)""" results = bes_conn.session_relevance_string(session_relevance) logging.info("Info on Relays:\n%s", results) From a65e366b775bf0cbbc3e35b8ac1d56420d3d13f5 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 14:43:56 -0500 Subject: [PATCH 216/231] some tweaks for targeting --- examples/action_and_monitor.py | 9 +++++++- examples/content/SetRelayOverride.bes | 32 +++++++++++++++++++++++++++ examples/relay_info.py | 4 ++-- 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 examples/content/SetRelayOverride.bes diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 1d3cc88..65b4846 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -107,6 +107,10 @@ def get_target_xml(targets=""): # if targets is int: if isinstance(targets, int): + if targets == 0: + raise ValueError( + "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 " + ) return f"{targets}" # if targets is str: @@ -319,7 +323,10 @@ def main(): bes_conn = besapi.plugin_utilities.get_besapi_connection(args) - action_and_monitor(bes_conn, args.file) + # set targeting criteria: + targets = 0 + + action_and_monitor(bes_conn, args.file, targets) logging.info("---------- END -----------") diff --git a/examples/content/SetRelayOverride.bes b/examples/content/SetRelayOverride.bes new file mode 100644 index 0000000..b9ab958 --- /dev/null +++ b/examples/content/SetRelayOverride.bes @@ -0,0 +1,32 @@ + + + + set relay name override + + exists relay service + not exists main gather service + not exists settings "_BESClient_Relay_NameOverride" of client + + Internal + jgstew + + + + + + x-fixlet-modification-time + Tue, 03 Aug 2021 15:18:27 +0000 + + BESC + + + Click + here + to deploy this action. + + + + + diff --git a/examples/relay_info.py b/examples/relay_info.py index a8e6089..dd9cc99 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -113,9 +113,9 @@ def main(): logging.info("---------- Starting New Session -----------") logging.debug("invoke folder: %s", invoke_folder) - logging.debug("Python version: %s", platform.sys.version) + logging.debug("%s's version: %s", get_invoke_file_name(verbose), __version__) logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) - logging.debug("this plugin's version: %s", __version__) + logging.debug("Python version: %s", platform.sys.version) bes_conn = besapi.plugin_utilities.get_besapi_connection(args) From 116abc1dd851be12a2edd870baada2f8c4565a3a Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 15:17:55 -0500 Subject: [PATCH 217/231] add example --- examples/content/RelaySelect.bes | 29 +++++++++++++++++++ ...- Universal.bes => TestEcho-Universal.bes} | 0 2 files changed, 29 insertions(+) create mode 100644 examples/content/RelaySelect.bes rename examples/content/{Test Echo - Universal.bes => TestEcho-Universal.bes} (100%) diff --git a/examples/content/RelaySelect.bes b/examples/content/RelaySelect.bes new file mode 100644 index 0000000..dfc1a1f --- /dev/null +++ b/examples/content/RelaySelect.bes @@ -0,0 +1,29 @@ + + + + RelaySelect + + + + Internal + jgstew + 2021-08-03 + + + + + x-fixlet-modification-time + Tue, 03 Aug 2021 15:18:27 +0000 + + BESC + + + Click + here + to deploy this action. + + + + + diff --git a/examples/content/Test Echo - Universal.bes b/examples/content/TestEcho-Universal.bes similarity index 100% rename from examples/content/Test Echo - Universal.bes rename to examples/content/TestEcho-Universal.bes From f648dc52a2ecfdd0cb00408207867b9226019650 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 15:20:34 -0500 Subject: [PATCH 218/231] rename example --- .../content/{SetRelayOverride.bes => RelaySetNameOverride.bes} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/content/{SetRelayOverride.bes => RelaySetNameOverride.bes} (100%) diff --git a/examples/content/SetRelayOverride.bes b/examples/content/RelaySetNameOverride.bes similarity index 100% rename from examples/content/SetRelayOverride.bes rename to examples/content/RelaySetNameOverride.bes From 1a7d15e3c34fec47c67b33a41d9d9767072ec808 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 15:26:04 -0500 Subject: [PATCH 219/231] tweak comment --- examples/action_and_monitor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 65b4846..6066dc2 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -323,7 +323,7 @@ def main(): bes_conn = besapi.plugin_utilities.get_besapi_connection(args) - # set targeting criteria: + # set targeting criteria to computer id int or "" or array targets = 0 action_and_monitor(bes_conn, args.file, targets) From 4d3a306148bddbd4dd1439826c0affb41f4241ea Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 15:33:59 -0500 Subject: [PATCH 220/231] remove comment --- examples/action_and_monitor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 6066dc2..09eb3f3 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -147,7 +147,6 @@ def get_target_xml(targets=""): def action_from_bes_file(bes_conn, file_path, targets=""): """create action from bes file with fixlet or task""" - # TODO: allow input variable for custom targeting # default to empty string: custom_relevance_xml = "" From 075a7fff90f74a58fc61d92e3429fc55e4b8f392 Mon Sep 17 00:00:00 2001 From: JGStew Date: Tue, 25 Feb 2025 15:35:42 -0500 Subject: [PATCH 221/231] tweak comments --- examples/action_and_monitor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 09eb3f3..1ac106f 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -7,7 +7,9 @@ NOTE: this script requires besapi v3.3.3+ due to use of besapi.plugin_utilities Example Usage: -python3 examples/action_and_monitor.py -c -vv --file './examples/content/Test Echo - Universal.bes' +python3 examples/action_and_monitor.py -c -vv --file './examples/content/TestEcho-Universal.bes' + +Inspect examples/action_and_monitor.log for results """ import logging From 089b59c996c7671bf507f11e2d46b7515cfe4159 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 26 Feb 2025 08:57:05 -0500 Subject: [PATCH 222/231] tweak logging level --- src/besapi/besapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index 44cdfbc..a2fae71 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -927,7 +927,7 @@ def __init__(self, request): f"\n - HTTP Response Status Code: `403` Forbidden\n - ERROR: `{self.text}`\n - URL: `{self.request.url}`" ) - besapi_logger.info( + besapi_logger.debug( "HTTP Request Status Code `%d` from URL `%s`", self.request.status_code, self.request.url, From 452060b210d9fca3ad19c1521a723b7ee86a4a10 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 26 Feb 2025 08:57:26 -0500 Subject: [PATCH 223/231] add relay affiliation to relay info --- examples/content/RelaySetAffiliationGroup.bes | 32 +++++++++++++++++++ examples/relay_info.py | 5 +++ 2 files changed, 37 insertions(+) create mode 100644 examples/content/RelaySetAffiliationGroup.bes diff --git a/examples/content/RelaySetAffiliationGroup.bes b/examples/content/RelaySetAffiliationGroup.bes new file mode 100644 index 0000000..7d91058 --- /dev/null +++ b/examples/content/RelaySetAffiliationGroup.bes @@ -0,0 +1,32 @@ + + + + set relay affiliation group + + exists relay service + not exists main gather service + not exists settings "_BESRelay_Register_Affiliation_AdvertisementList" of client + + Internal + jgstew + + + + + + x-fixlet-modification-time + Tue, 03 Aug 2021 15:18:27 +0000 + + BESC + + + Click + here + to deploy this action. + + + + + diff --git a/examples/relay_info.py b/examples/relay_info.py index dd9cc99..ea72a72 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -138,6 +138,11 @@ def main(): logging.info("Relay name override values:\n%s", results) + session_relevance = f"""(multiplicity of it, it) of unique values of values of client settings whose(name of it = "_BESRelay_Register_Affiliation_AdvertisementList") of bes computers whose(now - last report time of it < {last_report_days_filter} * day)""" + results = bes_conn.session_relevance_string(session_relevance) + + logging.info("Relay_Register_Affiliation values:\n%s", results) + # this should require MO: results = bes_conn.get("admin/masthead/parameters") From 7a43b6fb90b3d4edb56d3a7e6b4f04e7b5b1e564 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 26 Feb 2025 09:44:45 -0500 Subject: [PATCH 224/231] tweak relay select --- examples/content/RelaySelect.bes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/content/RelaySelect.bes b/examples/content/RelaySelect.bes index dfc1a1f..78d2117 100644 --- a/examples/content/RelaySelect.bes +++ b/examples/content/RelaySelect.bes @@ -3,6 +3,8 @@ RelaySelect + not exists relay service + not exists main gather service Internal From 66ff248050d14056f36529f8cdcd1a41403b550c Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 26 Feb 2025 10:22:16 -0500 Subject: [PATCH 225/231] add ability to do single action --- examples/action_and_monitor.py | 33 ++++++++++++++----- examples/content/RelaySelectAction.bes | 10 ++++++ .../{RelaySelect.bes => RelaySelectTask.bes} | 0 3 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 examples/content/RelaySelectAction.bes rename examples/content/{RelaySelect.bes => RelaySelectTask.bes} (100%) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 1ac106f..5e014e8 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -155,7 +155,9 @@ def action_from_bes_file(bes_conn, file_path, targets=""): tree = lxml.etree.parse(file_path) # //BES/*[self::Task or self::Fixlet]/*[@id='elid']/name() - bes_type = str(tree.xpath("//BES/*[self::Task or self::Fixlet]")[0].tag) + bes_type = str( + tree.xpath("//BES/*[self::Task or self::Fixlet or self::SingleAction]")[0].tag + ) logging.debug("BES Type: %s", bes_type) @@ -163,14 +165,25 @@ def action_from_bes_file(bes_conn, file_path, targets=""): logging.debug("Title: %s", title) - actionscript = tree.xpath(f"//BES/{bes_type}/DefaultAction/ActionScript/text()")[0] + try: + actionscript = tree.xpath( + f"//BES/{bes_type}/DefaultAction/ActionScript/text()" + )[0] + except IndexError: + # handle SingleAction case: + actionscript = tree.xpath(f"//BES/{bes_type}/ActionScript/text()")[0] logging.debug("ActionScript: %s", actionscript) try: - success_criteria = tree.xpath( - f"//BES/{bes_type}/DefaultAction/SuccessCriteria/@Option" - )[0] + if bes_type != "SingleAction": + success_criteria = tree.xpath( + f"//BES/{bes_type}/DefaultAction/SuccessCriteria/@Option" + )[0] + else: + success_criteria = tree.xpath(f"//BES/{bes_type}/SuccessCriteria/@Option")[ + 0 + ] except IndexError: # set success criteria if missing: (default) success_criteria = "RunToCompletion" @@ -179,9 +192,13 @@ def action_from_bes_file(bes_conn, file_path, targets=""): success_criteria = "OriginalRelevance" if success_criteria == "CustomRelevance": - custom_relevance = tree.xpath( - f"//BES/{bes_type}/DefaultAction/SuccessCriteria/text()" - )[0] + if bes_type != "SingleAction": + custom_relevance = tree.xpath( + f"//BES/{bes_type}/DefaultAction/SuccessCriteria/text()" + )[0] + else: + custom_relevance = tree.xpath(f"//BES/{bes_type}/SuccessCriteria/text()")[0] + custom_relevance_xml = f"" logging.debug("success_criteria: %s", success_criteria) diff --git a/examples/content/RelaySelectAction.bes b/examples/content/RelaySelectAction.bes new file mode 100644 index 0000000..e202889 --- /dev/null +++ b/examples/content/RelaySelectAction.bes @@ -0,0 +1,10 @@ + + + + Relay Select + + relay select + + false + + diff --git a/examples/content/RelaySelect.bes b/examples/content/RelaySelectTask.bes similarity index 100% rename from examples/content/RelaySelect.bes rename to examples/content/RelaySelectTask.bes From 54456b99457946578f81c1a0e7b93fe2365073b0 Mon Sep 17 00:00:00 2001 From: JGStew Date: Wed, 26 Feb 2025 10:32:22 -0500 Subject: [PATCH 226/231] add client seek list info --- examples/relay_info.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/relay_info.py b/examples/relay_info.py index ea72a72..e2e924b 100644 --- a/examples/relay_info.py +++ b/examples/relay_info.py @@ -143,6 +143,11 @@ def main(): logging.info("Relay_Register_Affiliation values:\n%s", results) + session_relevance = f"""(multiplicity of it, it) of unique values of values of client settings whose(name of it = "_BESClient_Register_Affiliation_SeekList") of bes computers whose(now - last report time of it < {last_report_days_filter} * day)""" + results = bes_conn.session_relevance_string(session_relevance) + + logging.info("Client_Register_Affiliation_Seek values:\n%s", results) + # this should require MO: results = bes_conn.get("admin/masthead/parameters") From 3115ba62e809e5854a1caea33a409137f13b39c0 Mon Sep 17 00:00:00 2001 From: JGStew Date: Fri, 28 Feb 2025 09:37:16 -0500 Subject: [PATCH 227/231] handle edge cases in targeting xml --- examples/action_and_monitor.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/action_and_monitor.py b/examples/action_and_monitor.py index 5e014e8..1182949 100644 --- a/examples/action_and_monitor.py +++ b/examples/action_and_monitor.py @@ -106,6 +106,10 @@ def get_target_xml(targets=""): - Array of Strings: ComputerName - Array of Integers: ComputerID """ + if targets is None or not targets: + logging.warning("No valid targeting found, will target no computers.") + # default if invalid: + return "False" # if targets is int: if isinstance(targets, int): @@ -119,6 +123,10 @@ def get_target_xml(targets=""): if isinstance(targets, str): # if targets string starts with "": if targets.startswith(""): + if "false" in targets.lower(): + # In my testing, false does not work correctly + return "False" + # return "false" return "true" # treat as custom relevance: return f"" From 35b1066bb57fe5516d760e0f8259b5bcd71b1867 Mon Sep 17 00:00:00 2001 From: JGStew Date: Fri, 28 Feb 2025 09:56:23 -0500 Subject: [PATCH 228/231] add boilerplate code to allow session relevance from file to take command line input --- examples/session_relevance_from_file.py | 101 +++++++++++++++++++++++- 1 file changed, 97 insertions(+), 4 deletions(-) diff --git a/examples/session_relevance_from_file.py b/examples/session_relevance_from_file.py index bad0e29..78703e5 100644 --- a/examples/session_relevance_from_file.py +++ b/examples/session_relevance_from_file.py @@ -4,25 +4,118 @@ requires `besapi`, install with command `pip install besapi` """ +import logging +import ntpath +import os +import platform +import sys + import besapi +import besapi.plugin_utilities + +__version__ = "1.2.1" +verbose = 0 +invoke_folder = None + + +def get_invoke_folder(verbose=0): + """Get the folder the script was invoked from""" + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_folder = os.path.abspath(os.path.dirname(sys.executable)) + else: + if verbose: + print("running in a normal Python process") + invoke_folder = os.path.abspath(os.path.dirname(__file__)) + + if verbose: + print(f"invoke_folder = {invoke_folder}") + + return invoke_folder + + +def get_invoke_file_name(verbose=0): + """Get the filename the script was invoked from""" + # using logging here won't actually log it to the file: + + if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): + if verbose: + print("running in a PyInstaller bundle") + invoke_file_path = sys.executable + else: + if verbose: + print("running in a normal Python process") + invoke_file_path = __file__ + + if verbose: + print(f"invoke_file_path = {invoke_file_path}") + + # get just the file name, return without file extension: + return os.path.splitext(ntpath.basename(invoke_file_path))[0] def main(): """Execution starts here""" print("main()") - bes_conn = besapi.besapi.get_bes_conn_using_config_file() - bes_conn.login() + print("NOTE: this script requires besapi v3.3.3+ due to besapi.plugin_utilities") - with open("examples/session_relevance_query_input.txt") as file: + parser = besapi.plugin_utilities.setup_plugin_argparse() + + # add additonal arg specific to this script: + parser.add_argument( + "-f", + "--file", + help="text file to read session relevance query from", + required=False, + type=str, + default="examples/session_relevance_query_input.txt", + ) + # allow unknown args to be parsed instead of throwing an error: + args, _unknown = parser.parse_known_args() + + # allow set global scoped vars + global verbose, invoke_folder + verbose = args.verbose + + # get folder the script was invoked from: + invoke_folder = get_invoke_folder() + + log_file_path = os.path.join( + get_invoke_folder(verbose), get_invoke_file_name(verbose) + ".log" + ) + + print(log_file_path) + + logging_config = besapi.plugin_utilities.get_plugin_logging_config( + log_file_path, verbose, args.console + ) + + logging.basicConfig(**logging_config) + + logging.info("---------- Starting New Session -----------") + logging.debug("invoke folder: %s", invoke_folder) + logging.debug("%s's version: %s", get_invoke_file_name(verbose), __version__) + logging.debug("BESAPI Module version: %s", besapi.besapi.__version__) + logging.debug("Python version: %s", platform.sys.version) + + bes_conn = besapi.plugin_utilities.get_besapi_connection(args) + + # args.file defaults to "examples/session_relevance_query_input.txt" + with open(args.file) as file: session_relevance = file.read() result = bes_conn.session_relevance_string(session_relevance) - print(result) + logging.debug(result) with open("examples/session_relevance_query_output.txt", "w") as file_out: file_out.write(result) + logging.info("---------- END -----------") + if __name__ == "__main__": main() From 8684a184d34a5cd6508427f9f80cd9f3aa3611e1 Mon Sep 17 00:00:00 2001 From: JGStew Date: Fri, 28 Feb 2025 14:11:40 -0500 Subject: [PATCH 229/231] update pre-commit config --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b76fb81..7be47b0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,11 +36,11 @@ repos: hooks: - id: isort - repo: https://github.com/psf/black - rev: 24.8.0 + rev: 25.1.0 hooks: - id: black - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.2 + rev: 0.31.2 hooks: - id: check-github-workflows args: ["--verbose"] From 7cc923928a5b33821ee723a5a1c1b2d91f72307f Mon Sep 17 00:00:00 2001 From: JGStew Date: Sun, 2 Mar 2025 13:10:50 -0500 Subject: [PATCH 230/231] add manual workflow for harper cli grammar checker (#4) * test grammar check * test again * tweak cargo command * test again * test * fix path * fix spelling errors, set workflow to manually run only --- .github/workflows/grammar-check.yaml | 20 ++++++++++++++++++++ src/besapi/besapi.py | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/grammar-check.yaml diff --git a/.github/workflows/grammar-check.yaml b/.github/workflows/grammar-check.yaml new file mode 100644 index 0000000..f966c43 --- /dev/null +++ b/.github/workflows/grammar-check.yaml @@ -0,0 +1,20 @@ +--- +name: grammar-check + +on: workflow_dispatch + +jobs: + grammar-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + target: x86_64-unknown-linux-gnu + + - name: install harper grammar checker + run: cargo install --locked --git https://github.com/Automattic/harper.git --branch master --tag v0.23.0 harper-cli + + - name: run harper grammar checker + run: harper-cli lint src/besapi/besapi.py diff --git a/src/besapi/besapi.py b/src/besapi/besapi.py index a2fae71..b218cca 100644 --- a/src/besapi/besapi.py +++ b/src/besapi/besapi.py @@ -120,7 +120,7 @@ def parse_bes_modtime(string_datetime): # """custom HTTPAdapter for requests to override blocksize # for Uploading or Downloading large files""" -# # override inti_poolmanager from regular HTTPAdapter +# # override init_poolmanager from regular HTTPAdapter # # https://stackoverflow.com/questions/22915295/python-requests-post-and-big-content/22915488#comment125583017_22915488 # def init_poolmanager(self, connections, maxsize, block=False, **pool_kwargs): # """Initializes a urllib3 PoolManager. @@ -669,7 +669,7 @@ def parse_upload_result_to_prefetch( file_url = str(result_upload.besobj.FileUpload.URL) if use_https: file_url = file_url.replace("http://", "https://") - # there are 3 different posibilities for the server FQDN + # there are 3 different possibilities for the server FQDN # localhost # self.rootserver (without port number) # the returned value from the upload result From f0fafdacc800a39374aca6bcdb497ced4bc3003f Mon Sep 17 00:00:00 2001 From: jgstew Date: Sun, 2 Mar 2025 13:21:10 -0500 Subject: [PATCH 231/231] test pre-commit hook --- .pre-commit-config.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7be47b0..5097f6c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,3 +49,7 @@ repos: hooks: - id: check-useless-excludes - id: check-hooks-apply + - repo: https://github.com/crate-ci/typos + rev: v1.30.0 + hooks: + - id: typos