From dcce816e42a8e9174eff634ea1a2c14dba83ed4e Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Sun, 21 Jan 2024 17:29:05 -0600 Subject: [PATCH 1/2] improve error handling and reporting when problems exist within the network transport layer --- pan_os_upgrade/upgrade.py | 127 ++++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 48 deletions(-) diff --git a/pan_os_upgrade/upgrade.py b/pan_os_upgrade/upgrade.py index afc8d84..6adbe23 100644 --- a/pan_os_upgrade/upgrade.py +++ b/pan_os_upgrade/upgrade.py @@ -68,8 +68,9 @@ from panos_upgrade_assurance.firewall_proxy import FirewallProxy # third party imports -import xmltodict +import dns.resolver import typer +import xmltodict # project imports from pan_os_upgrade.models import SnapshotReport, ReadinessCheckReport @@ -347,51 +348,81 @@ def get_emoji(action: str) -> str: # ---------------------------------------------------------------------------- -# Helper function to validate IP addresses +# Helper function to validate either the DNS hostname or IP address # ---------------------------------------------------------------------------- -def ip_callback(ip: str) -> str: +def resolve_hostname(hostname: str) -> bool: """ - Validates the input as a valid IP address. + Checks if a given hostname can be resolved via DNS query. - This function utilizes the ip_address function from the ipaddress standard library module to - validate the provided input. It is designed to be used as a callback function for Typer command-line - argument parsing, ensuring that only valid IP addresses are accepted as input for arguments where - this is a requirement. + This function attempts to resolve the specified hostname using DNS. It queries the DNS servers + that the operating system is configured to use. The function is designed to return a boolean + value indicating whether the hostname could be successfully resolved or not. Parameters ---------- - ip : str - A string representing the IP address to be validated. + hostname : str + The hostname (e.g., 'example.com') to be resolved. Returns ------- - str - The validated IP address string. + bool + Returns True if the hostname can be resolved, False otherwise. Raises ------ - typer.BadParameter - If the input string is not a valid IP address, a typer.BadParameter exception is raised with - an appropriate error message. + None + This function does not raise any exceptions. It handles all exceptions internally and + returns False in case of any issues during the resolution process. + """ + try: + dns.resolver.resolve(hostname) + return True + except (dns.resolver.NoAnswer, dns.resolver.NXDOMAIN, dns.exception.Timeout) as err: + # Optionally log or handle err here if needed + logging.debug(f"Hostname resolution failed: {err}") + return False - Example + +def ip_callback(value: str) -> str: + """ + Validates the input as a valid IP address or a resolvable hostname. + + This function first attempts to resolve the hostname via DNS query. If it fails, + it utilizes the ip_address function from the ipaddress standard library module to + validate the provided input as an IP address. It is designed to be used as a callback + function for Typer command-line argument parsing, ensuring that only valid IP addresses + or resolvable hostnames are accepted as input. + + Parameters + ---------- + value : str + A string representing the IP address or hostname to be validated. + + Returns ------- - @app.command() - def main(ip_address: str = typer.Argument(..., callback=ip_callback)): - # ip_address will be a validated IP address here. + str + The validated IP address string or hostname. - Notes - ----- - - The function assumes IPv4/IPv6 address validation is needed based on the context in which it's used. - - It's specifically tailored for use with Typer, enhancing command-line argument parsing. + Raises + ------ + typer.BadParameter + If the input string is not a valid IP address or a resolvable hostname, a typer.BadParameter + exception is raised with an appropriate error message. """ + # First, try to resolve as a hostname + if resolve_hostname(value): + return value + + # If hostname resolution fails, try as an IP address try: - ipaddress.ip_address(ip) - return ip + ipaddress.ip_address(value) + return value except ValueError as err: - raise typer.BadParameter("Please enter a valid IP address.") from err + raise typer.BadParameter( + "The value you passed for --hostname is neither a valid DNS hostname nor IP address, please check your inputs again." + ) from err # ---------------------------------------------------------------------------- @@ -538,7 +569,7 @@ def check_readiness_and_log( # Setting up connection to the Firewall appliance # ---------------------------------------------------------------------------- def connect_to_firewall( - ip_address: str, + hostname: str, api_username: str, api_password: str, ) -> Firewall: @@ -552,7 +583,7 @@ def connect_to_firewall( Parameters ---------- - - 'ip_address': The IP address of the Firewall appliance. + - 'hostname': The DNS hostname or IP address of the firewall appliance. - 'api_username': Username for authentication. - 'api_password': Password for authentication. @@ -575,7 +606,7 @@ def connect_to_firewall( """ try: target_device = PanDevice.create_from_device( - ip_address, + hostname, api_username, api_password, ) @@ -591,7 +622,7 @@ def connect_to_firewall( except PanConnectionTimeout: logging.error( - f"{get_emoji('error')} Connection to the firewall timed out. Please check the IP address and network connectivity." + f"{get_emoji('error')} Connection to the firewall timed out. Please check the DNS hostname or IP address and network connectivity." ) sys.exit(1) @@ -735,7 +766,7 @@ def software_update_check( Example -------- Checking if a specific PAN-OS version is available for download: - >>> firewall = Firewall(ip_address='192.168.1.1', api_username='admin', api_password='password') + >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password') >>> software_update_check(firewall, '10.1.0', ha_details={}) True or False depending on the availability of the version """ @@ -801,7 +832,7 @@ def get_ha_status(firewall: Firewall) -> Tuple[str, Optional[dict]]: Example ------- Retrieving HA status of a Firewall: - >>> firewall = Firewall(ip_address='192.168.1.1', api_username='admin', api_password='password') + >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password') >>> ha_status, ha_details = get_ha_status(firewall) >>> print(ha_status) 'active/passive' @@ -867,7 +898,7 @@ def software_download( Example -------- Initiating a PAN-OS version download: - >>> firewall = Firewall(ip_address='192.168.1.1', api_username='admin', api_password='password') + >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password') >>> software_download(firewall, '10.1.0', ha_details={}) True or False depending on the success of the download @@ -964,8 +995,8 @@ def run_assurance( ---------- firewall : Firewall The firewall instance on which to perform the operations. - ip_address : str - The ip_address of the firewall. + hostname : str + The ip address or dns hostname of the firewall. operation_type : str The type of operation to perform (e.g., 'readiness_check', 'state_snapshot', 'report'). actions : List[str] @@ -986,7 +1017,7 @@ def run_assurance( Example -------- Performing a state snapshot operation: - >>> firewall = Firewall(ip_address='192.168.1.1', 'admin', 'password') + >>> firewall = Firewall(hostname='192.168.1.1', 'admin', 'password') >>> run_assurance(firewall, 'firewall1', 'state_snapshot', ['arp_table', 'ip_sec_tunnels'], {}) SnapshotReport object or None @@ -1106,7 +1137,7 @@ def perform_snapshot( Example -------- Creating a network state snapshot: - >>> firewall = Firewall(ip_address='192.168.1.1', 'admin', 'password') + >>> firewall = Firewall(hostname='192.168.1.1', 'admin', 'password') >>> perform_snapshot(firewall, 'firewall1', '/path/to/snapshot.json') # Snapshot file is saved to the specified path. """ @@ -1185,7 +1216,7 @@ def perform_readiness_checks( Example -------- Conducting readiness checks: - >>> firewall = Firewall(ip_address='192.168.1.1', 'username', 'password') + >>> firewall = Firewall(hostname='192.168.1.1', 'username', 'password') >>> perform_readiness_checks(firewall, 'firewall1', '/path/to/readiness_report.json') # Readiness report is saved to the specified path. """ @@ -1271,7 +1302,7 @@ def backup_configuration( Example -------- Backing up the firewall configuration: - >>> firewall = Firewall(ip_address='192.168.1.1', 'admin', 'password') + >>> firewall = Firewall(hostname='192.168.1.1', 'admin', 'password') >>> backup_configuration(firewall, '/path/to/config_backup.xml') # Configuration is backed up to the specified file. """ @@ -1368,7 +1399,7 @@ def perform_upgrade( Example ------- Upgrading a firewall to a specific PAN-OS version: - >>> firewall = Firewall(ip_address='192.168.1.1', api_username='admin', api_password='password') + >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password') >>> perform_upgrade(firewall, '192.168.1.1', '10.2.0', max_retries=2, retry_interval=30) # The firewall is upgraded to PAN-OS version 10.2.0, with retries if necessary. """ @@ -1452,7 +1483,7 @@ def perform_reboot(firewall: Firewall, ha_details: Optional[dict] = None) -> Non Example ------- Rebooting a firewall and ensuring its operational status: - >>> firewall = Firewall(ip_address='192.168.1.1', api_username='admin', api_password='password') + >>> firewall = Firewall(hostname='192.168.1.1', api_username='admin', api_password='password') >>> perform_reboot(firewall) # The firewall undergoes a reboot and the script monitors until it's back online. """ @@ -1512,13 +1543,13 @@ def perform_reboot(firewall: Firewall, ha_details: Optional[dict] = None) -> Non # ---------------------------------------------------------------------------- @app.command() def main( - ip_address: Annotated[ + hostname: Annotated[ str, typer.Option( - "--ip-address", - "-i", - help="IP address of target firewall", - prompt="IP address", + "--hostname", + "-h", + help="Hostname or IP address of target firewall", + prompt="Hostname or IP address", callback=ip_callback, ), ], @@ -1589,7 +1620,7 @@ def main( Example Usage: ```bash - python upgrade.py --ip-address 192.168.1.1 --username admin --password secret --version 10.2.7 + python upgrade.py --hostname 192.168.1.1 --username admin --password secret --version 10.2.7 ``` This command will start the upgrade process for the firewall at '192.168.1.1' to version '10.2.7'. """ @@ -1612,7 +1643,7 @@ def main( # Create our connection to the firewall logging.debug(f"{get_emoji('start')} Connecting to PAN-OS firewall...") firewall = connect_to_firewall( - ip_address=ip_address, + hostname=hostname, api_username=username, api_password=password, ) From eaa4f51cff89a60f4115b4ca1422fd8809499538 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Sun, 21 Jan 2024 17:29:38 -0600 Subject: [PATCH 2/2] Add dnspython dependency --- poetry.lock | 105 +++++++++++++++++++++++++++++++++++++++++++------ pyproject.toml | 1 + 2 files changed, 93 insertions(+), 13 deletions(-) diff --git a/poetry.lock b/poetry.lock index 8ac7b0e..66df055 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,9 +1,10 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. [[package]] name = "annotated-types" version = "0.6.0" description = "Reusable constraint types to use with typing.Annotated" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -15,6 +16,7 @@ files = [ name = "asttokens" version = "2.4.1" description = "Annotate AST trees with source code positions" +category = "dev" optional = false python-versions = "*" files = [ @@ -33,6 +35,7 @@ test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] name = "babel" version = "2.14.0" description = "Internationalization utilities" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -47,6 +50,7 @@ dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] name = "black" version = "23.12.1" description = "The uncompromising code formatter." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -91,6 +95,7 @@ uvloop = ["uvloop (>=0.15.2)"] name = "certifi" version = "2023.11.17" description = "Python package for providing Mozilla's CA Bundle." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -102,6 +107,7 @@ files = [ name = "cffi" version = "1.16.0" description = "Foreign Function Interface for Python calling C code." +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -166,6 +172,7 @@ pycparser = "*" name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -265,6 +272,7 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -279,6 +287,7 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." +category = "main" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -290,6 +299,7 @@ files = [ name = "cryptography" version = "41.0.7" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -335,6 +345,7 @@ test-randomorder = ["pytest-randomly"] name = "decorator" version = "5.1.1" description = "Decorators for Humans" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -342,10 +353,32 @@ files = [ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] +[[package]] +name = "dnspython" +version = "2.5.0" +description = "DNS toolkit" +category = "main" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dnspython-2.5.0-py3-none-any.whl", hash = "sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6"}, + {file = "dnspython-2.5.0.tar.gz", hash = "sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=5.0.3)", "mypy (>=1.0.1)", "pylint (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "sphinx (>=7.0.0)", "twine (>=4.0.0)", "wheel (>=0.41.0)"] +dnssec = ["cryptography (>=41)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.25.1)"] +doq = ["aioquic (>=0.9.20)"] +idna = ["idna (>=2.1)"] +trio = ["trio (>=0.14)"] +wmi = ["wmi (>=1.5.1)"] + [[package]] name = "executing" version = "2.0.1" description = "Get the currently executing AST node of a frame, and other information" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -360,6 +393,7 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth name = "flake8" version = "7.0.0" description = "the modular source code checker: pep8 pyflakes and co" +category = "dev" optional = false python-versions = ">=3.8.1" files = [ @@ -376,6 +410,7 @@ pyflakes = ">=3.2.0,<3.3.0" name = "ghp-import" version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." +category = "dev" optional = false python-versions = "*" files = [ @@ -393,6 +428,7 @@ dev = ["flake8", "markdown", "twine", "wheel"] name = "idna" version = "3.6" description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -404,6 +440,7 @@ files = [ name = "ipdb" version = "0.13.13" description = "IPython-enabled pdb" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -419,6 +456,7 @@ ipython = {version = ">=7.31.1", markers = "python_version >= \"3.11\""} name = "ipython" version = "8.20.0" description = "IPython: Productive Interactive Computing" +category = "dev" optional = false python-versions = ">=3.10" files = [ @@ -454,6 +492,7 @@ test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pa name = "jedi" version = "0.19.1" description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -473,6 +512,7 @@ testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] name = "jinja2" version = "3.1.3" description = "A very fast and expressive template engine." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -490,6 +530,7 @@ i18n = ["Babel (>=2.7)"] name = "markdown" version = "3.5.2" description = "Python implementation of John Gruber's Markdown." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -505,6 +546,7 @@ testing = ["coverage", "pyyaml"] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -529,6 +571,7 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] name = "markupsafe" version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -598,6 +641,7 @@ files = [ name = "matplotlib-inline" version = "0.1.6" description = "Inline Matplotlib backend for Jupyter" +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -612,6 +656,7 @@ traitlets = "*" name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -623,6 +668,7 @@ files = [ name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -634,6 +680,7 @@ files = [ name = "mergedeep" version = "1.3.4" description = "A deep merge function for 🐍." +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -645,6 +692,7 @@ files = [ name = "mkdocs" version = "1.5.3" description = "Project documentation with Markdown." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -675,6 +723,7 @@ min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-imp name = "mkdocs-autorefs" version = "0.5.0" description = "Automatically link across pages in MkDocs." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -690,6 +739,7 @@ mkdocs = ">=1.1" name = "mkdocs-material" version = "9.5.4" description = "Documentation that simply works" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -719,6 +769,7 @@ recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2. name = "mkdocs-material-extensions" version = "1.3.1" description = "Extension pack for Python Markdown and MkDocs Material." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -730,6 +781,7 @@ files = [ name = "mkdocstrings" version = "0.24.0" description = "Automatic documentation from sources, for MkDocs." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -756,6 +808,7 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." +category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -767,6 +820,7 @@ files = [ name = "packaging" version = "23.2" description = "Core utilities for Python packages" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -778,6 +832,7 @@ files = [ name = "paginate" version = "0.5.6" description = "Divides large result sets into pages for easier browsing" +category = "dev" optional = false python-versions = "*" files = [ @@ -788,6 +843,7 @@ files = [ name = "pan-os-python" version = "1.11.0" description = "Framework for interacting with Palo Alto Networks devices via API" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" files = [ @@ -802,6 +858,7 @@ pan-python = ">=0.17.0,<0.18.0" name = "pan-python" version = "0.17.0" description = "Multi-tool set for Palo Alto Networks PAN-OS, Panorama, WildFire and AutoFocus" +category = "main" optional = false python-versions = "*" files = [ @@ -813,6 +870,7 @@ files = [ name = "panos-upgrade-assurance" version = "0.3.1" description = "" +category = "main" optional = false python-versions = ">=3.8,<4.0" files = [ @@ -830,6 +888,7 @@ xmltodict = ">=0.12,<0.13" name = "parso" version = "0.8.3" description = "A Python Parser" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -845,6 +904,7 @@ testing = ["docopt", "pytest (<6.0.0)"] name = "pathspec" version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -856,6 +916,7 @@ files = [ name = "pexpect" version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." +category = "dev" optional = false python-versions = "*" files = [ @@ -870,6 +931,7 @@ ptyprocess = ">=0.5" name = "platformdirs" version = "4.1.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -885,6 +947,7 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co name = "prompt-toolkit" version = "3.0.43" description = "Library for building powerful interactive command lines in Python" +category = "dev" optional = false python-versions = ">=3.7.0" files = [ @@ -899,6 +962,7 @@ wcwidth = "*" name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" +category = "dev" optional = false python-versions = "*" files = [ @@ -910,6 +974,7 @@ files = [ name = "pure-eval" version = "0.2.2" description = "Safely evaluate AST nodes without side effects" +category = "dev" optional = false python-versions = "*" files = [ @@ -924,6 +989,7 @@ tests = ["pytest"] name = "pycodestyle" version = "2.11.1" description = "Python style guide checker" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -935,6 +1001,7 @@ files = [ name = "pycparser" version = "2.21" description = "C parser in Python" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -946,6 +1013,7 @@ files = [ name = "pydantic" version = "2.5.3" description = "Data validation using Python type hints" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -965,6 +1033,7 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.14.6" description = "" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1082,6 +1151,7 @@ typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" name = "pyflakes" version = "3.2.0" description = "passive checker of Python programs" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1093,6 +1163,7 @@ files = [ name = "pygments" version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1108,6 +1179,7 @@ windows-terminal = ["colorama (>=0.4.6)"] name = "pymdown-extensions" version = "10.7" description = "Extension pack for Python Markdown." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1126,6 +1198,7 @@ extra = ["pygments (>=2.12)"] name = "pyopenssl" version = "23.3.0" description = "Python wrapper module around the OpenSSL library" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1144,6 +1217,7 @@ test = ["flaky", "pretend", "pytest (>=3.0.1)"] name = "python-dateutil" version = "2.8.2" description = "Extensions to the standard Python datetime module" +category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" files = [ @@ -1158,6 +1232,7 @@ six = ">=1.5" name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1166,7 +1241,6 @@ files = [ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, - {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, @@ -1174,16 +1248,8 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, - {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, @@ -1200,7 +1266,6 @@ files = [ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, - {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, @@ -1208,7 +1273,6 @@ files = [ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, - {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, @@ -1218,6 +1282,7 @@ files = [ name = "pyyaml-env-tag" version = "0.1" description = "A custom YAML tag for referencing environment variables in YAML files. " +category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -1232,6 +1297,7 @@ pyyaml = "*" name = "regex" version = "2023.12.25" description = "Alternative regular expression module, to replace re." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1334,6 +1400,7 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1355,6 +1422,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] name = "rich" version = "13.7.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" optional = false python-versions = ">=3.7.0" files = [ @@ -1373,6 +1441,7 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] name = "setuptools" version = "69.0.3" description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1389,6 +1458,7 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar name = "shellingham" version = "1.5.4" description = "Tool to Detect Surrounding Shell" +category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1400,6 +1470,7 @@ files = [ name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" +category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ @@ -1411,6 +1482,7 @@ files = [ name = "stack-data" version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" +category = "dev" optional = false python-versions = "*" files = [ @@ -1430,6 +1502,7 @@ tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] name = "traitlets" version = "5.14.1" description = "Traitlets Python configuration system" +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1445,6 +1518,7 @@ test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0, name = "typer" version = "0.9.0" description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +category = "main" optional = false python-versions = ">=3.6" files = [ @@ -1469,6 +1543,7 @@ test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6. name = "typing-extensions" version = "4.9.0" description = "Backported and Experimental Type Hints for Python 3.8+" +category = "main" optional = false python-versions = ">=3.8" files = [ @@ -1480,6 +1555,7 @@ files = [ name = "urllib3" version = "2.1.0" description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -1496,6 +1572,7 @@ zstd = ["zstandard (>=0.18.0)"] name = "watchdog" version = "3.0.0" description = "Filesystem events monitoring" +category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1535,6 +1612,7 @@ watchmedo = ["PyYAML (>=3.10)"] name = "wcwidth" version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" optional = false python-versions = "*" files = [ @@ -1546,6 +1624,7 @@ files = [ name = "xmltodict" version = "0.12.0" description = "Makes working with XML feel like you are working with JSON" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ @@ -1556,4 +1635,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "785058e7a6e23533348b35b881c84ccca9d28798fd799649c8cff127470f9147" +content-hash = "06190cc634a064236f30e49a609d5bc5e62645e0ba05a3c4653b2dcf8b923f06" diff --git a/pyproject.toml b/pyproject.toml index cadfb88..b1ec6cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ panos-upgrade-assurance = "^0.3.1" pydantic = "^2.5.3" typer = { extras = ["all"], version = "^0.9.0" } setuptools = "^69.0.3" +dnspython = "^2.5.0" [tool.poetry.group.dev.dependencies] black = "^23.12.1"