From 8aa55a46814ee2130b4d62d1deaab336cb1f8aef Mon Sep 17 00:00:00 2001 From: Elliott Saille Date: Tue, 3 Dec 2024 21:19:09 +0000 Subject: [PATCH 1/4] feat: allow defining arbitrary python interpreter, better error messages for newer versions python implementing EXTERNALLY-MANAGED --- .gitignore | 225 +++++++++++++++++++++++++++++++++-- contents/winrm-check.py | 71 +++++++++-- contents/winrm-exec.py | 68 +++++++++-- contents/winrm-filecopier.py | 72 +++++++++-- plugin.yaml | 3 +- 5 files changed, 393 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index 2e4115b..066cd83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,231 @@ -# Created by https://www.gitignore.io/api/java,gradle +# Created by https://www.toptal.com/developers/gitignore/api/python,java,gradle +# Edit at https://www.toptal.com/developers/gitignore?templates=python,java,gradle +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json ### Gradle ### .gradle -.idea -/build/ -/docker/rundeck/plugins/py-winrm-plugin-* -/docker/resources/*.xml +**/build/ +!src/**/build/ + # Ignore Gradle GUI config gradle-app.setting # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + # Cache of project .gradletasknamecache -# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 -# gradle/wrapper/gradle-wrapper.properties +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Gradle Patch ### +# Java heap dump +*.hprof -# End of https://www.gitignore.io/api/java,gradle \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/python,java,gradle + +/docker/rundeck/plugins/py-winrm-plugin-* +/docker/resources/*.xml \ No newline at end of file diff --git a/contents/winrm-check.py b/contents/winrm-check.py index 70a97a3..09c9378 100644 --- a/contents/winrm-check.py +++ b/contents/winrm-check.py @@ -9,7 +9,8 @@ import colored_formatter from colored_formatter import ColoredFormatter import kerberosauth - +import sysconfig +import os.path #checking and importing dependencies ISPY3 = sys.version_info[0] == 3 @@ -19,6 +20,7 @@ HAS_NTLM = False HAS_CREDSSP = False HAS_PEXPECT = False +SYSTEM_INTERPRETER = False if ISPY3: from inspect import getfullargspec as getargspec @@ -70,6 +72,12 @@ except ImportError as e: HAS_PEXPECT = False +try: + externally_managed_path = os.path.join(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED") + SYSTEM_INTERPRETER = os.path.exists(externally_managed_path) +except: + SYSTEM_INTERPRETER = False + log_level = 'INFO' if os.environ.get('RD_JOB_LOGLEVEL') == 'DEBUG': log_level = 'DEBUG' @@ -207,28 +215,67 @@ log.debug("------------------------------------------") +URLLIB_ERRORMESSAGE_BASE = "requests and urllib3 not installed" +WINRM_ERRORMESSAGE_BASE = "pywinrm not installed" +KRB_ERRORMESSAGE_BASE = "requests-kerberos not installed" +PEXPECT_ERRORMESSAGE_BASE = "pexpect not installed" +CREDSSP_ERRORMESSAGE_BASE = "pywinrm[credssp] not installed" +NTLM_ERRORMESSAGE_BASE = "requests-ntlm not installed" + +if SYSTEM_INTERPRETER: + import configparser + externally_managed_file = configparser.RawConfigParser() + externally_managed_file.read(externally_managed_path) + SYSTEM_INTERPRETER_ERRORMESSAGE=externally_managed_file.get("externally-managed", "Error") + + ERRORMESSAGE = ", please install it using your systems package manager or consider using a virtual environment.\n{}".format(SYSTEM_INTERPRETER_ERRORMESSAGE) + URLLIB_ERRORMESSAGE = "{}{}".format(URLLIB_ERRORMESSAGE_BASE, ERRORMESSAGE) + WINRM_ERRORMESSAGE = "{}{}".format(WINRM_ERRORMESSAGE_BASE, ERRORMESSAGE) + KRB_ERRORMESSAGE = "{}{}".format(KRB_ERRORMESSAGE_BASE, ERRORMESSAGE) + PEXPECT_ERRORMESSAGE = "{}{}".format(PEXPECT_ERRORMESSAGE_BASE, ERRORMESSAGE) + CREDSSP_ERRORMESSAGE = "{}{}".format(CREDSSP_ERRORMESSAGE_BASE, ERRORMESSAGE) + NTLM_ERRORMESSAGE = "{}{}".format(NTLM_ERRORMESSAGE_BASE, ERRORMESSAGE) +else: + python_executable_basename = os.path.dirname(sys.executable) + python_pip_executable = os.path.join(python_executable_basename, "pip3" if ISPY3 else "pip") + URLLIB_ERRORMESSAGE = "{}, try: {} install requests urllib3".format(URLLIB_ERRORMESSAGE_BASE, python_pip_executable) + WINRM_ERRORMESSAGE = "{}, try: {} install pywinrm".format(WINRM_ERRORMESSAGE_BASE, python_pip_executable) + KRB_ERRORMESSAGE = "{}, try: {} install requests-kerberos".format(KRB_ERRORMESSAGE_BASE, python_pip_executable) + PEXPECT_ERRORMESSAGE = "{}, try: {} install pexpect".format(PEXPECT_ERRORMESSAGE_BASE, python_pip_executable) + CREDSSP_ERRORMESSAGE = "{}, try: {} install pywinrm[credssp]".format(CREDSSP_ERRORMESSAGE_BASE, python_pip_executable) + NTLM_ERRORMESSAGE = "{}, try: {} install requests-ntlm".format(NTLM_ERRORMESSAGE_BASE, python_pip_executable) + + +if enabledHttpDebug: + httpclient_logging_patch(logging.DEBUG) + +PACKAGE_ERROR = False + if not URLLIB_INSTALLED: - log.error("request and urllib3 not installed, try: pip install requests && pip install urllib3") - sys.exit(1) + log.error(URLLIB_ERRORMESSAGE) + PACKAGE_ERROR = True if not WINRM_INSTALLED: - log.error("winrm not installed, try: pip install pywinrm") - sys.exit(1) + log.error(WINRM_ERRORMESSAGE) + PACKAGE_ERROR = True if authentication == "kerberos" and not KRB_INSTALLED: - log.error("Kerberos not installed, try: pip install requests-kerberos") - sys.exit(1) + log.error(KRB_ERRORMESSAGE) + PACKAGE_ERROR = True if authentication == "kerberos" and not HAS_PEXPECT: - log.error("pexpect not installed, try: pip install pexpect") - sys.exit(1) + log.error(PEXPECT_ERRORMESSAGE) + PACKAGE_ERROR = True if authentication == "credssp" and not HAS_CREDSSP: - log.error("CredSSP not installed, try: pip install pywinrm[credssp]") - sys.exit(1) + log.error(CREDSSP_ERRORMESSAGE) + PACKAGE_ERROR = True if authentication == "ntlm" and not HAS_NTLM: - log.error("NTLM not installed, try: pip install requests_ntlm") + log.error(NTLM_ERRORMESSAGE) + PACKAGE_ERROR = True + +if PACKAGE_ERROR: sys.exit(1) arguments={} diff --git a/contents/winrm-exec.py b/contents/winrm-exec.py index a70de9f..edb2d5e 100755 --- a/contents/winrm-exec.py +++ b/contents/winrm-exec.py @@ -12,6 +12,8 @@ import kerberosauth import common from colored_formatter import ColoredFormatter +import sysconfig +import os.path class SuppressFilter(logging.Filter): def filter(self, record): @@ -34,7 +36,6 @@ def httpclient_log(*args): http.client.print = httpclient_log http.client.HTTPConnection.debuglevel = 1 - #checking and importing dependencies ISPY3 = sys.version_info[0] == 3 WINRM_INSTALLED = False @@ -43,6 +44,7 @@ def httpclient_log(*args): HAS_NTLM = False HAS_CREDSSP = False HAS_PEXPECT = False +SYSTEM_INTERPRETER = False if ISPY3: from inspect import getfullargspec as getargspec @@ -94,6 +96,12 @@ def httpclient_log(*args): except ImportError as e: HAS_PEXPECT = False +try: + externally_managed_path = os.path.join(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED") + SYSTEM_INTERPRETER = os.path.exists(externally_managed_path) +except: + SYSTEM_INTERPRETER = False + if os.environ.get('RD_JOB_LOGLEVEL') == 'DEBUG': log_level = 'DEBUG' else: @@ -271,31 +279,67 @@ def httpclient_log(*args): log.debug("retryConnectionDelay: " + str(retryconnectiondelay)) log.debug("------------------------------------------") +URLLIB_ERRORMESSAGE_BASE = "requests and urllib3 not installed" +WINRM_ERRORMESSAGE_BASE = "pywinrm not installed" +KRB_ERRORMESSAGE_BASE = "requests-kerberos not installed" +PEXPECT_ERRORMESSAGE_BASE = "pexpect not installed" +CREDSSP_ERRORMESSAGE_BASE = "pywinrm[credssp] not installed" +NTLM_ERRORMESSAGE_BASE = "requests-ntlm not installed" + +if SYSTEM_INTERPRETER: + import configparser + externally_managed_file = configparser.RawConfigParser() + externally_managed_file.read(externally_managed_path) + SYSTEM_INTERPRETER_ERRORMESSAGE=externally_managed_file.get("externally-managed", "Error") + + ERRORMESSAGE = ", please install it using your systems package manager or consider using a virtual environment.\n{}".format(SYSTEM_INTERPRETER_ERRORMESSAGE) + URLLIB_ERRORMESSAGE = "{}{}".format(URLLIB_ERRORMESSAGE_BASE, ERRORMESSAGE) + WINRM_ERRORMESSAGE = "{}{}".format(WINRM_ERRORMESSAGE_BASE, ERRORMESSAGE) + KRB_ERRORMESSAGE = "{}{}".format(KRB_ERRORMESSAGE_BASE, ERRORMESSAGE) + PEXPECT_ERRORMESSAGE = "{}{}".format(PEXPECT_ERRORMESSAGE_BASE, ERRORMESSAGE) + CREDSSP_ERRORMESSAGE = "{}{}".format(CREDSSP_ERRORMESSAGE_BASE, ERRORMESSAGE) + NTLM_ERRORMESSAGE = "{}{}".format(NTLM_ERRORMESSAGE_BASE, ERRORMESSAGE) +else: + python_executable_basename = os.path.dirname(sys.executable) + python_pip_executable = os.path.join(python_executable_basename, "pip3" if ISPY3 else "pip") + URLLIB_ERRORMESSAGE = "{}, try: {} install requests urllib3".format(URLLIB_ERRORMESSAGE_BASE, python_pip_executable) + WINRM_ERRORMESSAGE = "{}, try: {} install pywinrm".format(WINRM_ERRORMESSAGE_BASE, python_pip_executable) + KRB_ERRORMESSAGE = "{}, try: {} install requests-kerberos".format(KRB_ERRORMESSAGE_BASE, python_pip_executable) + PEXPECT_ERRORMESSAGE = "{}, try: {} install pexpect".format(PEXPECT_ERRORMESSAGE_BASE, python_pip_executable) + CREDSSP_ERRORMESSAGE = "{}, try: {} install pywinrm[credssp]".format(CREDSSP_ERRORMESSAGE_BASE, python_pip_executable) + NTLM_ERRORMESSAGE = "{}, try: {} install requests-ntlm".format(NTLM_ERRORMESSAGE_BASE, python_pip_executable) + + if enabledHttpDebug: httpclient_logging_patch(logging.DEBUG) +PACKAGE_ERROR = False + if not URLLIB_INSTALLED: - log.error("request and urllib3 not installed, try: pip install requests && pip install urllib3") - sys.exit(1) + log.error(URLLIB_ERRORMESSAGE) + PACKAGE_ERROR = True if not WINRM_INSTALLED: - log.error("winrm not installed, try: pip install pywinrm") - sys.exit(1) + log.error(WINRM_ERRORMESSAGE) + PACKAGE_ERROR = True if authentication == "kerberos" and not KRB_INSTALLED: - log.error("Kerberos not installed, try: pip install requests-kerberos") - sys.exit(1) + log.error(KRB_ERRORMESSAGE) + PACKAGE_ERROR = True if authentication == "kerberos" and not HAS_PEXPECT: - log.error("pexpect not installed, try: pip install pexpect") - sys.exit(1) + log.error(PEXPECT_ERRORMESSAGE) + PACKAGE_ERROR = True if authentication == "credssp" and not HAS_CREDSSP: - log.error("CredSSP not installed, try: pip install pywinrm[credssp]") - sys.exit(1) + log.error(CREDSSP_ERRORMESSAGE) + PACKAGE_ERROR = True if authentication == "ntlm" and not HAS_NTLM: - log.error("NTLM not installed, try: pip install requests_ntlm") + log.error(NTLM_ERRORMESSAGE) + PACKAGE_ERROR = True + +if PACKAGE_ERROR: sys.exit(1) arguments = {} diff --git a/contents/winrm-filecopier.py b/contents/winrm-filecopier.py index 784fe73..bfbbd25 100644 --- a/contents/winrm-filecopier.py +++ b/contents/winrm-filecopier.py @@ -17,8 +17,10 @@ import kerberosauth import http.client import winrm_session +import sysconfig +import os.path -# checking and importing dependencies +#checking and importing dependencies ISPY3 = sys.version_info[0] == 3 WINRM_INSTALLED = False URLLIB_INSTALLED = False @@ -26,6 +28,7 @@ HAS_NTLM = False HAS_CREDSSP = False HAS_PEXPECT = False +SYSTEM_INTERPRETER = False if ISPY3: from inspect import getfullargspec as getargspec @@ -77,6 +80,12 @@ except ImportError as e: HAS_PEXPECT = False +try: + externally_managed_path = os.path.join(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED") + SYSTEM_INTERPRETER = os.path.exists(externally_managed_path) +except: + SYSTEM_INTERPRETER = False + log_level = 'INFO' if os.environ.get('RD_JOB_LOGLEVEL') == 'DEBUG': log_level = 'DEBUG' @@ -376,28 +385,67 @@ def winrm_upload(self, if(operationtimeout): arguments["operation_timeout_sec"] = operationtimeout +URLLIB_ERRORMESSAGE_BASE = "requests and urllib3 not installed" +WINRM_ERRORMESSAGE_BASE = "pywinrm not installed" +KRB_ERRORMESSAGE_BASE = "requests-kerberos not installed" +PEXPECT_ERRORMESSAGE_BASE = "pexpect not installed" +CREDSSP_ERRORMESSAGE_BASE = "pywinrm[credssp] not installed" +NTLM_ERRORMESSAGE_BASE = "requests-ntlm not installed" + +if SYSTEM_INTERPRETER: + import configparser + externally_managed_file = configparser.RawConfigParser() + externally_managed_file.read(externally_managed_path) + SYSTEM_INTERPRETER_ERRORMESSAGE=externally_managed_file.get("externally-managed", "Error") + + ERRORMESSAGE = ", please install it using your systems package manager or consider using a virtual environment.\n{}".format(SYSTEM_INTERPRETER_ERRORMESSAGE) + URLLIB_ERRORMESSAGE = "{}{}".format(URLLIB_ERRORMESSAGE_BASE, ERRORMESSAGE) + WINRM_ERRORMESSAGE = "{}{}".format(WINRM_ERRORMESSAGE_BASE, ERRORMESSAGE) + KRB_ERRORMESSAGE = "{}{}".format(KRB_ERRORMESSAGE_BASE, ERRORMESSAGE) + PEXPECT_ERRORMESSAGE = "{}{}".format(PEXPECT_ERRORMESSAGE_BASE, ERRORMESSAGE) + CREDSSP_ERRORMESSAGE = "{}{}".format(CREDSSP_ERRORMESSAGE_BASE, ERRORMESSAGE) + NTLM_ERRORMESSAGE = "{}{}".format(NTLM_ERRORMESSAGE_BASE, ERRORMESSAGE) +else: + python_executable_basename = os.path.dirname(sys.executable) + python_pip_executable = os.path.join(python_executable_basename, "pip3" if ISPY3 else "pip") + URLLIB_ERRORMESSAGE = "{}, try: {} install requests urllib3".format(URLLIB_ERRORMESSAGE_BASE, python_pip_executable) + WINRM_ERRORMESSAGE = "{}, try: {} install pywinrm".format(WINRM_ERRORMESSAGE_BASE, python_pip_executable) + KRB_ERRORMESSAGE = "{}, try: {} install requests-kerberos".format(KRB_ERRORMESSAGE_BASE, python_pip_executable) + PEXPECT_ERRORMESSAGE = "{}, try: {} install pexpect".format(PEXPECT_ERRORMESSAGE_BASE, python_pip_executable) + CREDSSP_ERRORMESSAGE = "{}, try: {} install pywinrm[credssp]".format(CREDSSP_ERRORMESSAGE_BASE, python_pip_executable) + NTLM_ERRORMESSAGE = "{}, try: {} install requests-ntlm".format(NTLM_ERRORMESSAGE_BASE, python_pip_executable) + + +if enabledHttpDebug: + httpclient_logging_patch(logging.DEBUG) + +PACKAGE_ERROR = False + if not URLLIB_INSTALLED: - log.error("request and urllib3 not installed, try: pip install requests && pip install urllib3") - sys.exit(1) + log.error(URLLIB_ERRORMESSAGE) + PACKAGE_ERROR = True if not WINRM_INSTALLED: - log.error("winrm not installed, try: pip install pywinrm") - sys.exit(1) + log.error(WINRM_ERRORMESSAGE) + PACKAGE_ERROR = True if authentication == "kerberos" and not KRB_INSTALLED: - log.error("Kerberos not installed, try: pip install pywinrm[kerberos]") - sys.exit(1) + log.error(KRB_ERRORMESSAGE) + PACKAGE_ERROR = True if authentication == "kerberos" and not HAS_PEXPECT: - log.error("pexpect not installed, try: pip install pexpect") - sys.exit(1) + log.error(PEXPECT_ERRORMESSAGE) + PACKAGE_ERROR = True if authentication == "credssp" and not HAS_CREDSSP: - log.error("CredSSP not installed, try: pip install pywinrm[credssp]") - sys.exit(1) + log.error(CREDSSP_ERRORMESSAGE) + PACKAGE_ERROR = True if authentication == "ntlm" and not HAS_NTLM: - log.error("NTLM not installed, try: pip install requests_ntlm") + log.error(NTLM_ERRORMESSAGE) + PACKAGE_ERROR = True + +if PACKAGE_ERROR: sys.exit(1) if authentication == "kerberos": diff --git a/plugin.yaml b/plugin.yaml index d2f5334..eb663d7 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -25,8 +25,7 @@ providers: - name: interpreter title: Python Interpreter description: "Python Interpreter (Default: python)" - type: Select - values: "python,python2,python3" + type: String default: "python" required: true scope: Instance From 3eff89f9f5b67bf38176445b62b2ee7955edcf51 Mon Sep 17 00:00:00 2001 From: Elliott Saille Date: Tue, 3 Dec 2024 21:28:57 +0000 Subject: [PATCH 2/4] feat: updated messages to use python -m instead of calling the pip executable --- contents/winrm-check.py | 14 ++++++-------- contents/winrm-exec.py | 14 ++++++-------- contents/winrm-filecopier.py | 14 ++++++-------- 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/contents/winrm-check.py b/contents/winrm-check.py index 09c9378..6578319 100644 --- a/contents/winrm-check.py +++ b/contents/winrm-check.py @@ -236,14 +236,12 @@ CREDSSP_ERRORMESSAGE = "{}{}".format(CREDSSP_ERRORMESSAGE_BASE, ERRORMESSAGE) NTLM_ERRORMESSAGE = "{}{}".format(NTLM_ERRORMESSAGE_BASE, ERRORMESSAGE) else: - python_executable_basename = os.path.dirname(sys.executable) - python_pip_executable = os.path.join(python_executable_basename, "pip3" if ISPY3 else "pip") - URLLIB_ERRORMESSAGE = "{}, try: {} install requests urllib3".format(URLLIB_ERRORMESSAGE_BASE, python_pip_executable) - WINRM_ERRORMESSAGE = "{}, try: {} install pywinrm".format(WINRM_ERRORMESSAGE_BASE, python_pip_executable) - KRB_ERRORMESSAGE = "{}, try: {} install requests-kerberos".format(KRB_ERRORMESSAGE_BASE, python_pip_executable) - PEXPECT_ERRORMESSAGE = "{}, try: {} install pexpect".format(PEXPECT_ERRORMESSAGE_BASE, python_pip_executable) - CREDSSP_ERRORMESSAGE = "{}, try: {} install pywinrm[credssp]".format(CREDSSP_ERRORMESSAGE_BASE, python_pip_executable) - NTLM_ERRORMESSAGE = "{}, try: {} install requests-ntlm".format(NTLM_ERRORMESSAGE_BASE, python_pip_executable) + URLLIB_ERRORMESSAGE = "{}, try: {} -m pip install requests urllib3".format(URLLIB_ERRORMESSAGE_BASE, sys.executable) + WINRM_ERRORMESSAGE = "{}, try: {} -m pip install pywinrm".format(WINRM_ERRORMESSAGE_BASE, sys.executable) + KRB_ERRORMESSAGE = "{}, try: {} -m pip install requests-kerberos".format(KRB_ERRORMESSAGE_BASE, sys.executable) + PEXPECT_ERRORMESSAGE = "{}, try: {} -m pip install pexpect".format(PEXPECT_ERRORMESSAGE_BASE, sys.executable) + CREDSSP_ERRORMESSAGE = "{}, try: {} -m pip install pywinrm[credssp]".format(CREDSSP_ERRORMESSAGE_BASE, sys.executable) + NTLM_ERRORMESSAGE = "{}, try: {} -m pip install requests-ntlm".format(NTLM_ERRORMESSAGE_BASE, sys.executable) if enabledHttpDebug: diff --git a/contents/winrm-exec.py b/contents/winrm-exec.py index edb2d5e..9943c29 100755 --- a/contents/winrm-exec.py +++ b/contents/winrm-exec.py @@ -300,14 +300,12 @@ def httpclient_log(*args): CREDSSP_ERRORMESSAGE = "{}{}".format(CREDSSP_ERRORMESSAGE_BASE, ERRORMESSAGE) NTLM_ERRORMESSAGE = "{}{}".format(NTLM_ERRORMESSAGE_BASE, ERRORMESSAGE) else: - python_executable_basename = os.path.dirname(sys.executable) - python_pip_executable = os.path.join(python_executable_basename, "pip3" if ISPY3 else "pip") - URLLIB_ERRORMESSAGE = "{}, try: {} install requests urllib3".format(URLLIB_ERRORMESSAGE_BASE, python_pip_executable) - WINRM_ERRORMESSAGE = "{}, try: {} install pywinrm".format(WINRM_ERRORMESSAGE_BASE, python_pip_executable) - KRB_ERRORMESSAGE = "{}, try: {} install requests-kerberos".format(KRB_ERRORMESSAGE_BASE, python_pip_executable) - PEXPECT_ERRORMESSAGE = "{}, try: {} install pexpect".format(PEXPECT_ERRORMESSAGE_BASE, python_pip_executable) - CREDSSP_ERRORMESSAGE = "{}, try: {} install pywinrm[credssp]".format(CREDSSP_ERRORMESSAGE_BASE, python_pip_executable) - NTLM_ERRORMESSAGE = "{}, try: {} install requests-ntlm".format(NTLM_ERRORMESSAGE_BASE, python_pip_executable) + URLLIB_ERRORMESSAGE = "{}, try: {} -m pip install requests urllib3".format(URLLIB_ERRORMESSAGE_BASE, sys.executable) + WINRM_ERRORMESSAGE = "{}, try: {} -m pip install pywinrm".format(WINRM_ERRORMESSAGE_BASE, sys.executable) + KRB_ERRORMESSAGE = "{}, try: {} -m pip install requests-kerberos".format(KRB_ERRORMESSAGE_BASE, sys.executable) + PEXPECT_ERRORMESSAGE = "{}, try: {} -m pip install pexpect".format(PEXPECT_ERRORMESSAGE_BASE, sys.executable) + CREDSSP_ERRORMESSAGE = "{}, try: {} -m pip install pywinrm[credssp]".format(CREDSSP_ERRORMESSAGE_BASE, sys.executable) + NTLM_ERRORMESSAGE = "{}, try: {} -m pip install requests-ntlm".format(NTLM_ERRORMESSAGE_BASE, sys.executable) if enabledHttpDebug: diff --git a/contents/winrm-filecopier.py b/contents/winrm-filecopier.py index bfbbd25..72c6a7a 100644 --- a/contents/winrm-filecopier.py +++ b/contents/winrm-filecopier.py @@ -406,14 +406,12 @@ def winrm_upload(self, CREDSSP_ERRORMESSAGE = "{}{}".format(CREDSSP_ERRORMESSAGE_BASE, ERRORMESSAGE) NTLM_ERRORMESSAGE = "{}{}".format(NTLM_ERRORMESSAGE_BASE, ERRORMESSAGE) else: - python_executable_basename = os.path.dirname(sys.executable) - python_pip_executable = os.path.join(python_executable_basename, "pip3" if ISPY3 else "pip") - URLLIB_ERRORMESSAGE = "{}, try: {} install requests urllib3".format(URLLIB_ERRORMESSAGE_BASE, python_pip_executable) - WINRM_ERRORMESSAGE = "{}, try: {} install pywinrm".format(WINRM_ERRORMESSAGE_BASE, python_pip_executable) - KRB_ERRORMESSAGE = "{}, try: {} install requests-kerberos".format(KRB_ERRORMESSAGE_BASE, python_pip_executable) - PEXPECT_ERRORMESSAGE = "{}, try: {} install pexpect".format(PEXPECT_ERRORMESSAGE_BASE, python_pip_executable) - CREDSSP_ERRORMESSAGE = "{}, try: {} install pywinrm[credssp]".format(CREDSSP_ERRORMESSAGE_BASE, python_pip_executable) - NTLM_ERRORMESSAGE = "{}, try: {} install requests-ntlm".format(NTLM_ERRORMESSAGE_BASE, python_pip_executable) + URLLIB_ERRORMESSAGE = "{}, try: {} -m pip install requests urllib3".format(URLLIB_ERRORMESSAGE_BASE, sys.executable) + WINRM_ERRORMESSAGE = "{}, try: {} -m pip install pywinrm".format(WINRM_ERRORMESSAGE_BASE, sys.executable) + KRB_ERRORMESSAGE = "{}, try: {} -m pip install requests-kerberos".format(KRB_ERRORMESSAGE_BASE, sys.executable) + PEXPECT_ERRORMESSAGE = "{}, try: {} -m pip install pexpect".format(PEXPECT_ERRORMESSAGE_BASE, sys.executable) + CREDSSP_ERRORMESSAGE = "{}, try: {} -m pip install pywinrm[credssp]".format(CREDSSP_ERRORMESSAGE_BASE, sys.executable) + NTLM_ERRORMESSAGE = "{}, try: {} -m pip install requests-ntlm".format(NTLM_ERRORMESSAGE_BASE, sys.executable) if enabledHttpDebug: From c3c72eb1047ebd8bf198fd630b3757f64e312b90 Mon Sep 17 00:00:00 2001 From: Elliott Saille Date: Wed, 25 Mar 2026 15:20:22 -0500 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- contents/winrm-check.py | 12 +++++++++--- contents/winrm-exec.py | 37 +++++++++++++++++++++++++++++++----- contents/winrm-filecopier.py | 18 +++++++++++------- 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/contents/winrm-check.py b/contents/winrm-check.py index 6578319..96cd998 100644 --- a/contents/winrm-check.py +++ b/contents/winrm-check.py @@ -75,7 +75,10 @@ try: externally_managed_path = os.path.join(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED") SYSTEM_INTERPRETER = os.path.exists(externally_managed_path) -except: +except Exception as e: + logging.getLogger(__name__).debug( + "Failed to determine externally-managed status: %s", e, exc_info=True + ) SYSTEM_INTERPRETER = False log_level = 'INFO' @@ -225,8 +228,11 @@ if SYSTEM_INTERPRETER: import configparser externally_managed_file = configparser.RawConfigParser() - externally_managed_file.read(externally_managed_path) - SYSTEM_INTERPRETER_ERRORMESSAGE=externally_managed_file.get("externally-managed", "Error") + try: + externally_managed_file.read(externally_managed_path) + SYSTEM_INTERPRETER_ERRORMESSAGE = externally_managed_file.get("externally-managed", "Error") + except configparser.Error: + SYSTEM_INTERPRETER_ERRORMESSAGE = "This Python interpreter is externally managed. Please refer to your system's EXTERNALLY-MANAGED file or documentation for details." ERRORMESSAGE = ", please install it using your systems package manager or consider using a virtual environment.\n{}".format(SYSTEM_INTERPRETER_ERRORMESSAGE) URLLIB_ERRORMESSAGE = "{}{}".format(URLLIB_ERRORMESSAGE_BASE, ERRORMESSAGE) diff --git a/contents/winrm-exec.py b/contents/winrm-exec.py index 9943c29..496fec2 100755 --- a/contents/winrm-exec.py +++ b/contents/winrm-exec.py @@ -1,7 +1,7 @@ import argparse try: import os; os.environ['PATH'] -except: +except Exception: import os os.environ.setdefault('PATH', '') import sys @@ -22,7 +22,7 @@ def filter(self, record): try: from urllib3.connectionpool import log #log.addFilter(SuppressFilter()) -except: +except ImportError: pass import http.client @@ -99,7 +99,11 @@ def httpclient_log(*args): try: externally_managed_path = os.path.join(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED") SYSTEM_INTERPRETER = os.path.exists(externally_managed_path) -except: +except Exception: + logging.getLogger(__name__).debug( + "Failed to detect externally-managed Python environment; assuming non-system interpreter", + exc_info=True, + ) SYSTEM_INTERPRETER = False if os.environ.get('RD_JOB_LOGLEVEL') == 'DEBUG': @@ -289,8 +293,31 @@ def httpclient_log(*args): if SYSTEM_INTERPRETER: import configparser externally_managed_file = configparser.RawConfigParser() - externally_managed_file.read(externally_managed_path) - SYSTEM_INTERPRETER_ERRORMESSAGE=externally_managed_file.get("externally-managed", "Error") + SYSTEM_INTERPRETER_ERRORMESSAGE = "" + try: + # Attempt to read the EXTERNALLY-MANAGED file and extract the error message. + externally_managed_file.read(externally_managed_path) + SYSTEM_INTERPRETER_ERRORMESSAGE = externally_managed_file.get( + "externally-managed", + "Error", + fallback="" + ) + except (configparser.NoSectionError, + configparser.NoOptionError, + configparser.MissingSectionHeaderError, + configparser.ParsingError, + OSError): + # If the file is missing, malformed, or does not have the expected + # section/key, fall back to a generic message instead of crashing. + SYSTEM_INTERPRETER_ERRORMESSAGE = "" + + if not SYSTEM_INTERPRETER_ERRORMESSAGE: + SYSTEM_INTERPRETER_ERRORMESSAGE = ( + "Python packages are externally managed by your system. " + "Please refer to your operating system's package manager " + "documentation for instructions on installing additional " + "Python packages when using the system interpreter." + ) ERRORMESSAGE = ", please install it using your systems package manager or consider using a virtual environment.\n{}".format(SYSTEM_INTERPRETER_ERRORMESSAGE) URLLIB_ERRORMESSAGE = "{}{}".format(URLLIB_ERRORMESSAGE_BASE, ERRORMESSAGE) diff --git a/contents/winrm-filecopier.py b/contents/winrm-filecopier.py index 72c6a7a..4c56359 100644 --- a/contents/winrm-filecopier.py +++ b/contents/winrm-filecopier.py @@ -3,7 +3,6 @@ except: import os os.environ.setdefault('PATH', '') -import winrm import argparse import sys import base64 @@ -83,7 +82,13 @@ try: externally_managed_path = os.path.join(sysconfig.get_path("stdlib"), "EXTERNALLY-MANAGED") SYSTEM_INTERPRETER = os.path.exists(externally_managed_path) -except: +except Exception as e: + logger = logging.getLogger(__name__) + logger.debug( + "Failed to detect externally-managed system interpreter using path %r: %s", + externally_managed_path if 'externally_managed_path' in locals() else None, + e, + ) SYSTEM_INTERPRETER = False log_level = 'INFO' @@ -396,7 +401,10 @@ def winrm_upload(self, import configparser externally_managed_file = configparser.RawConfigParser() externally_managed_file.read(externally_managed_path) - SYSTEM_INTERPRETER_ERRORMESSAGE=externally_managed_file.get("externally-managed", "Error") + try: + SYSTEM_INTERPRETER_ERRORMESSAGE = externally_managed_file.get("externally-managed", "Error") + except (configparser.NoSectionError, configparser.NoOptionError): + SYSTEM_INTERPRETER_ERRORMESSAGE = "Python on this system is externally managed by your operating system or distribution. Please use your system package manager to install additional Python packages, or consider using a virtual environment." ERRORMESSAGE = ", please install it using your systems package manager or consider using a virtual environment.\n{}".format(SYSTEM_INTERPRETER_ERRORMESSAGE) URLLIB_ERRORMESSAGE = "{}{}".format(URLLIB_ERRORMESSAGE_BASE, ERRORMESSAGE) @@ -413,10 +421,6 @@ def winrm_upload(self, CREDSSP_ERRORMESSAGE = "{}, try: {} -m pip install pywinrm[credssp]".format(CREDSSP_ERRORMESSAGE_BASE, sys.executable) NTLM_ERRORMESSAGE = "{}, try: {} -m pip install requests-ntlm".format(NTLM_ERRORMESSAGE_BASE, sys.executable) - -if enabledHttpDebug: - httpclient_logging_patch(logging.DEBUG) - PACKAGE_ERROR = False if not URLLIB_INSTALLED: From fea175943c5c47858cdd9494c0bccc82518d7eb4 Mon Sep 17 00:00:00 2001 From: Elliott Saille Date: Wed, 25 Mar 2026 15:23:43 -0500 Subject: [PATCH 4/4] Update interpreter config type to String Changed interpreter type from Select to String in plugin.yaml. --- plugin.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/plugin.yaml b/plugin.yaml index eb663d7..2f2fad8 100644 --- a/plugin.yaml +++ b/plugin.yaml @@ -233,8 +233,7 @@ providers: - name: interpreter title: Python Interpreter description: "Python Interpreter (Default: python)" - type: Select - values: "python,python2,python3" + type: String default: "python" required: true scope: Instance @@ -421,8 +420,7 @@ providers: - name: interpreter title: Python Interpreter description: "Python Interpreter (Default: python)" - type: Select - values: "python,python2,python3" + type: String default: "python" required: true scope: Instance @@ -511,4 +509,4 @@ providers: default: "false" required: false renderingOptions: - groupName: Kerberos \ No newline at end of file + groupName: Kerberos