From 8b4d132de6bd8ef6908e6c4cebffc2ea1e9327bf Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Sun, 21 Jan 2024 15:13:29 +0000 Subject: [PATCH 1/5] adding setuptools for python3.12 users and typer for argparse replacement --- poetry.lock | 182 +++++++++++++++++++++++++++++++------------------ pyproject.toml | 2 + 2 files changed, 119 insertions(+), 65 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9c84fe5..8ac7b0e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,10 +1,9 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 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 = [ @@ -16,7 +15,6 @@ files = [ name = "asttokens" version = "2.4.1" description = "Annotate AST trees with source code positions" -category = "dev" optional = false python-versions = "*" files = [ @@ -35,7 +33,6 @@ 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 = [ @@ -50,7 +47,6 @@ 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 = [ @@ -95,7 +91,6 @@ 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 = [ @@ -107,7 +102,6 @@ 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 = [ @@ -172,7 +166,6 @@ 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 = [ @@ -272,7 +265,6 @@ files = [ name = "click" version = "8.1.7" description = "Composable command line interface toolkit" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -287,7 +279,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""} name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." -category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ @@ -299,7 +290,6 @@ 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 = [ @@ -345,7 +335,6 @@ test-randomorder = ["pytest-randomly"] name = "decorator" version = "5.1.1" description = "Decorators for Humans" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -357,7 +346,6 @@ files = [ 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 = [ @@ -372,7 +360,6 @@ 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 = [ @@ -389,7 +376,6 @@ 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 = [ @@ -407,7 +393,6 @@ 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 = [ @@ -419,7 +404,6 @@ 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 = [ @@ -435,7 +419,6 @@ 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 = [ @@ -471,7 +454,6 @@ 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 = [ @@ -491,7 +473,6 @@ 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 = [ @@ -509,7 +490,6 @@ 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 = [ @@ -521,11 +501,34 @@ files = [ docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"] testing = ["coverage", "pyyaml"] +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "markupsafe" version = "2.1.4" description = "Safely add untrusted strings to HTML/XML markup." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -595,7 +598,6 @@ files = [ name = "matplotlib-inline" version = "0.1.6" description = "Inline Matplotlib backend for Jupyter" -category = "dev" optional = false python-versions = ">=3.5" files = [ @@ -610,7 +612,6 @@ traitlets = "*" name = "mccabe" version = "0.7.0" description = "McCabe checker, plugin for flake8" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -618,11 +619,21 @@ files = [ {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, ] +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "mergedeep" version = "1.3.4" description = "A deep merge function for 🐍." -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -634,7 +645,6 @@ files = [ name = "mkdocs" version = "1.5.3" description = "Project documentation with Markdown." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -665,7 +675,6 @@ 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 = [ @@ -681,7 +690,6 @@ mkdocs = ">=1.1" name = "mkdocs-material" version = "9.5.4" description = "Documentation that simply works" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -711,7 +719,6 @@ 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 = [ @@ -723,7 +730,6 @@ files = [ name = "mkdocstrings" version = "0.24.0" description = "Automatic documentation from sources, for MkDocs." -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -750,7 +756,6 @@ 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 = [ @@ -762,7 +767,6 @@ files = [ name = "packaging" version = "23.2" description = "Core utilities for Python packages" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -774,7 +778,6 @@ files = [ name = "paginate" version = "0.5.6" description = "Divides large result sets into pages for easier browsing" -category = "dev" optional = false python-versions = "*" files = [ @@ -785,7 +788,6 @@ 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 = [ @@ -800,7 +802,6 @@ 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 = [ @@ -812,7 +813,6 @@ files = [ name = "panos-upgrade-assurance" version = "0.3.1" description = "" -category = "main" optional = false python-versions = ">=3.8,<4.0" files = [ @@ -830,7 +830,6 @@ xmltodict = ">=0.12,<0.13" name = "parso" version = "0.8.3" description = "A Python Parser" -category = "dev" optional = false python-versions = ">=3.6" files = [ @@ -846,7 +845,6 @@ 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 = [ @@ -858,7 +856,6 @@ files = [ name = "pexpect" version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." -category = "dev" optional = false python-versions = "*" files = [ @@ -873,7 +870,6 @@ 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 = [ @@ -889,7 +885,6 @@ 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 = [ @@ -904,7 +899,6 @@ wcwidth = "*" name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" -category = "dev" optional = false python-versions = "*" files = [ @@ -916,7 +910,6 @@ files = [ name = "pure-eval" version = "0.2.2" description = "Safely evaluate AST nodes without side effects" -category = "dev" optional = false python-versions = "*" files = [ @@ -931,7 +924,6 @@ tests = ["pytest"] name = "pycodestyle" version = "2.11.1" description = "Python style guide checker" -category = "dev" optional = false python-versions = ">=3.8" files = [ @@ -943,7 +935,6 @@ 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 = [ @@ -955,7 +946,6 @@ files = [ name = "pydantic" version = "2.5.3" description = "Data validation using Python type hints" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -975,7 +965,6 @@ email = ["email-validator (>=2.0.0)"] name = "pydantic-core" version = "2.14.6" description = "" -category = "main" optional = false python-versions = ">=3.7" files = [ @@ -1093,7 +1082,6 @@ 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 = [ @@ -1105,7 +1093,6 @@ files = [ name = "pygments" version = "2.17.2" description = "Pygments is a syntax highlighting package written in Python." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1121,7 +1108,6 @@ 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 = [ @@ -1140,7 +1126,6 @@ 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 = [ @@ -1159,7 +1144,6 @@ 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 = [ @@ -1174,7 +1158,6 @@ 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 = [ @@ -1183,6 +1166,7 @@ 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"}, @@ -1190,8 +1174,16 @@ 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"}, @@ -1208,6 +1200,7 @@ 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"}, @@ -1215,6 +1208,7 @@ 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"}, @@ -1224,7 +1218,6 @@ 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 = [ @@ -1239,7 +1232,6 @@ pyyaml = "*" name = "regex" version = "2023.12.25" description = "Alternative regular expression module, to replace re." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1342,7 +1334,6 @@ files = [ name = "requests" version = "2.31.0" description = "Python HTTP for Humans." -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1360,11 +1351,55 @@ urllib3 = ">=1.21.1,<3" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "rich" +version = "13.7.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.7.0-py3-none-any.whl", hash = "sha256:6da14c108c4866ee9520bbffa71f6fe3962e193b7da68720583850cd4548e235"}, + {file = "rich-13.7.0.tar.gz", hash = "sha256:5cb5123b5cf9ee70584244246816e9114227e0b98ad9176eede6ad54bf5403fa"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "setuptools" +version = "69.0.3" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, + {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + [[package]] 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 = [ @@ -1376,7 +1411,6 @@ 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 = [ @@ -1396,7 +1430,6 @@ 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 = [ @@ -1408,11 +1441,34 @@ files = [ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<7.5)", "pytest-mock", "pytest-mypy-testing"] +[[package]] +name = "typer" +version = "0.9.0" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.6" +files = [ + {file = "typer-0.9.0-py3-none-any.whl", hash = "sha256:5d96d986a21493606a358cae4461bd8cdf83cbf33a5aa950ae629ca3b51467ee"}, + {file = "typer-0.9.0.tar.gz", hash = "sha256:50922fd79aea2f4751a8e0408ff10d2662bd0c8bbfa84755a699f3bada2978b2"}, +] + +[package.dependencies] +click = ">=7.1.1,<9.0.0" +colorama = {version = ">=0.4.3,<0.5.0", optional = true, markers = "extra == \"all\""} +rich = {version = ">=10.11.0,<14.0.0", optional = true, markers = "extra == \"all\""} +shellingham = {version = ">=1.3.0,<2.0.0", optional = true, markers = "extra == \"all\""} +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["colorama (>=0.4.3,<0.5.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2.17.0,<3.0.0)"] +doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] +test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] + [[package]] 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 = [ @@ -1424,7 +1480,6 @@ 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 = [ @@ -1441,7 +1496,6 @@ zstd = ["zstandard (>=0.18.0)"] name = "watchdog" version = "3.0.0" description = "Filesystem events monitoring" -category = "dev" optional = false python-versions = ">=3.7" files = [ @@ -1481,7 +1535,6 @@ 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 = [ @@ -1493,7 +1546,6 @@ 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 = [ @@ -1504,4 +1556,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "7bd6349f3a249622c0f6f7f78daed72550b49903ca6b40549a9ea188b8751cc7" +content-hash = "785058e7a6e23533348b35b881c84ccca9d28798fd799649c8cff127470f9147" diff --git a/pyproject.toml b/pyproject.toml index 50d90bd..f17fe92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,8 @@ python = "^3.11" pan-os-python = "^1.11.0" panos-upgrade-assurance = "^0.3.1" pydantic = "^2.5.3" +typer = {extras = ["all"], version = "^0.9.0"} +setuptools = "^69.0.3" [tool.poetry.group.dev.dependencies] black = "^23.12.1" From 43a11ff67776338fc0e39adb27501205710ed33b Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Sun, 21 Jan 2024 15:47:52 +0000 Subject: [PATCH 2/5] replacing argparse with typer, remove api_key and .env connectivity options. --- pan_os_upgrade/upgrade.py | 565 ++++++++++++++------------------------ 1 file changed, 199 insertions(+), 366 deletions(-) diff --git a/pan_os_upgrade/upgrade.py b/pan_os_upgrade/upgrade.py index 432aed5..a37ba87 100644 --- a/pan_os_upgrade/upgrade.py +++ b/pan_os_upgrade/upgrade.py @@ -1,14 +1,17 @@ """ upgrade.py: A script to automate the upgrade process of PAN-OS firewalls. -This module contains the functionality to perform automated upgrade procedures on Palo Alto Networks firewalls. -It includes handling for various PAN-OS operations, system settings management, error handling specific to PAN-OS, -and interactions with the panos-upgrade-assurance tool. The module is designed to be used as a standalone script or -integrated into larger automation workflows. +This module contains functionality to perform automated upgrade procedures on Palo Alto Networks firewalls. +It handles various PAN-OS operations, system settings management, error handling specific to PAN-OS, +and interactions with the panos-upgrade-assurance tool. The script is intended for use as a standalone utility or +as part of larger automation workflows. It uses the Typer library for command-line interface creation, replacing +the previous argparse implementation. Authentication is now exclusively username/password-based, with no option for +API key authentication. Additionally, the script no longer searches for settings in a .env file but accepts necessary +parameters directly via command-line arguments. Imports: Standard Libraries: - argparse: For parsing command-line arguments. + ipaddress: For handling IP addresses. logging: For providing a logging interface. os: For interacting with the operating system. sys: For accessing system-specific parameters and functions. @@ -28,19 +31,21 @@ Third-party libraries: xmltodict: For converting XML data to Python dictionaries. + typer: For building command-line interface applications. BaseModel (pydantic): For creating Pydantic base models. Project-specific imports: SnapshotReport, ReadinessCheckReport (pan_os_upgrade.models): For handling snapshot and readiness check reports. """ # standard library imports -import argparse +import ipaddress import logging import os import sys import time from logging.handlers import RotatingFileHandler from typing import Dict, List, Optional, Tuple, Union +from typing_extensions import Annotated # trunk-ignore(bandit/B405) import xml.etree.ElementTree as ET @@ -64,7 +69,7 @@ # third party imports import xmltodict -from pydantic import BaseModel +import typer # project imports from pan_os_upgrade.models import SnapshotReport, ReadinessCheckReport @@ -73,24 +78,6 @@ # ---------------------------------------------------------------------------- # Define logging levels # ---------------------------------------------------------------------------- -# A dictionary mapping string representations of logging levels to their -# corresponding numeric values in the logging module. -# -# This dictionary is used to configure the logging level of the application -# based on user input or configuration settings. Each key is a string that -# represents a logging level, and the corresponding value is the numeric -# level from the logging module. -# -# Keys: -# debug (str): Corresponds to logging.DEBUG, for detailed diagnostic information. -# info (str): Corresponds to logging.INFO, for general informational messages. -# warning (str): Corresponds to logging.WARNING, for warning messages about potential issues. -# error (str): Corresponds to logging.ERROR, for error messages indicating a problem. -# critical (str): Corresponds to logging.CRITICAL, for critical issues that may prevent program execution. -# -# Example: -# To set the logging level to 'debug': -# logger.setLevel(LOGGING_LEVELS['debug']) LOGGING_LEVELS = { "debug": logging.DEBUG, "info": logging.INFO, @@ -247,256 +234,6 @@ class AssuranceOptions: ] -# ---------------------------------------------------------------------------- -# Define models -# ---------------------------------------------------------------------------- -class Args(BaseModel): - """ - Represents the command-line arguments for connecting and configuring a Firewall appliance. - - This class, utilizing Pydantic for data validation, ensures that the provided arguments meet - the expected data types and formats. It serves as a structured way to define and access - configuration settings and command-line options required for the operation of the upgrade script. - - Attributes - ---------- - api_key : Optional[str] - The API key used for authenticating with the Firewall appliance. If not provided, - defaults to None. This field is optional and mutually exclusive with username/password authentication. - - dry_run : bool - Indicates whether the script should perform a dry run without making actual changes. - Defaults to False. Useful for testing and validation purposes. - - hostname : Optional[str] - The hostname or IP address of the Firewall appliance. Required for establishing a connection. - If not provided, defaults to None. - - log_level : str - The logging level for the script's output. Valid options are 'debug', 'info', 'warning', - 'error', and 'critical'. Defaults to 'info'. This controls the verbosity of the script's logging. - - password : Optional[str] - The password for authentication with the Firewall appliance. Required if using username/password - authentication. Defaults to None. - - target_version : Optional[str] - The target PAN-OS version for the upgrade. Specifies the version to which the appliance should be upgraded. - If not provided, defaults to None. - - username : Optional[str] - The username for authentication with the Firewall appliance. Required if using username/password - authentication. Defaults to None. - - Example - ------- - Creating an instance of Args with command-line parameters: - >>> args = Args(api_key="yourapikey", hostname="192.168.1.1", target_version="10.0.1") - >>> print(args.hostname) - 192.168.1.1 - """ - - api_key: Optional[str] = None - dry_run: bool = False - hostname: Optional[str] = None - log_level: str = "info" - password: Optional[str] = None - target_version: Optional[str] = None - username: Optional[str] = None - - -# ---------------------------------------------------------------------------- -# Setting up environment variables based on the .env file or CLI arguments -# ---------------------------------------------------------------------------- -def load_environment_variables(file_path: str) -> None: - """ - Load key-value pairs as environment variables from a specified file. - - This function processes a file line by line, setting each key-value pair as an environment variable. - It ignores lines that start with a '#' as they are considered comments. The function is useful for - initializing environment variables from a configuration file, typically named '.env'. This allows for - dynamic configuration of the script based on external settings. - - Parameters - ---------- - file_path : str - The path to the file containing the environment variables. Each non-comment line in the file - should be in the format 'KEY=VALUE'. Comment lines should start with '#'. - - Raises - ------ - FileNotFoundError - If the file specified by 'file_path' does not exist, a FileNotFoundError is raised. - - Examples - -------- - Assuming a '.env' file with the following contents: - # Example .env file - PAN_USERNAME=admin - PAN_PASSWORD=password123 - API_KEY= - HOSTNAME=panorama.example.com - TARGET_VERSION=10.1.1 - LOG_LEVEL=debug - DRY_RUN=True - - Using the function to load these environment variables: - >>> load_environment_variables('.env') - # Environment variables are now set based on the contents of '.env'. - """ - if os.path.exists(file_path): - with open(file_path) as f: - for line in f: - if line.startswith("#") or not line.strip(): - continue - key, value = line.strip().split("=", 1) - os.environ[key] = value - - -# ---------------------------------------------------------------------------- -# Handling CLI arguments -# ---------------------------------------------------------------------------- -def parse_arguments() -> Args: - """ - Parses command-line arguments for configuring the Firewall appliance interaction. - - This function sets up an argument parser to define and process command-line arguments necessary for the script's - operation. It handles authentication details (like hostname, username, password, API key), operational flags (such as dry-run), - and logging level. If required arguments are not provided via the command line, the function attempts to load them from a `.env` file. - - The function ensures mutual exclusivity between using an API key and a username/password combination for authentication. - If crucial arguments like hostname or target version are missing, or if the authentication information is incomplete, - the script logs an error and exits. - - Returns - ------- - Args - An instance of the Args class, populated with the parsed arguments and environment variables. It contains - fields such as api_key, hostname, log_level, username, target_version, and password. - - Raises - ------ - SystemExit - - If essential arguments like hostname or target version are not provided either via CLI or in the .env file. - - If neither an API key nor both username and password are provided for authentication. - - Example - ------- - Command-line usage example: - $ python upgrade.py --hostname 192.168.0.1 --username admin --password secret --version 10.0.0 - - This would parse the arguments and return an Args instance with the specified values. - """ - # Load environment variables first - load_environment_variables(".env") - - parser = argparse.ArgumentParser( - description="This script interacts with a Firewall appliance to perform readiness checks, " - "snapshots, and configuration backups in before and after its upgrade. If arguments are not " - "provided, the script will attempt to load them from a .env file.", - epilog="For more information, visit https://cdot65.github.io/pan-os-upgrade.", - ) - - # Grouping authentication arguments - auth_group = parser.add_argument_group("Authentication") - auth_group.add_argument( - "--hostname", - dest="hostname", - type=str, - default=None, - help="Hostname of the PAN-OS appliance", - ) - auth_group.add_argument( - "--api-key", - dest="api_key", - type=str, - default=None, - help="API Key for authentication with the Firewall appliance.", - ) - auth_group.add_argument( - "--username", - dest="username", - type=str, - help="Username for authentication with the Firewall appliance.", - ) - auth_group.add_argument( - "--password", - dest="password", - type=str, - help="Password for authentication.", - ) - - # Other arguments - parser.add_argument( - "--dry-run", - dest="dry_run", - action="store_true", - default=os.getenv("DRY_RUN", "False").lower() == "true", - help="Perform a dry run of all tests and downloads without performing the actual upgrade.", - ) - parser.add_argument( - "--log-level", - dest="log_level", - choices=LOGGING_LEVELS.keys(), - default=os.getenv("LOG_LEVEL", "info"), - help="Set the logging output level", - ) - parser.add_argument( - "--version", - dest="target_version", - type=str, - default=None, - help="Target PAN-OS version to upgrade to", - ) - - args = parser.parse_args() - - # Load environment variables if necessary arguments are not provided - if not all([args.api_key, args.hostname, args.username, args.password]): - load_environment_variables(".env") - - # Create a new structure to store arguments with different variable names - arguments = { - "api_key": args.api_key or os.getenv("API_KEY"), - "dry_run": args.dry_run or os.getenv("DRY_RUN"), - "hostname": args.hostname or os.getenv("HOSTNAME"), - "pan_username": args.username or os.getenv("PAN_USERNAME"), - "pan_password": args.password or os.getenv("PAN_PASSWORD"), - "target_version": args.target_version or os.getenv("TARGET_VERSION"), - "log_level": args.log_level or os.getenv("LOG_LEVEL") or "info", - } - - # Check for missing hostname - if not arguments["hostname"]: - logging.error( - f"{get_emoji('error')} Hostname must be provided as a --hostname argument or in .env", - ) - - sys.exit(1) - - # Check for missing target version - if not arguments["target_version"]: - logging.error( - f"{get_emoji('error')} Target version must be provided as a --version argument or in .env", - ) - logging.error(f"{get_emoji('stop')} Halting script.") - - sys.exit(1) - - # Ensuring mutual exclusivity - if arguments["api_key"]: - arguments["pan_username"] = arguments["pan_password"] = None - elif not (arguments["pan_username"] and arguments["pan_password"]): - logging.error( - f"{get_emoji('error')} Provide either API key --api-key argument or both --username and --password", - ) - logging.error(f"{get_emoji('stop')} Halting script.") - - sys.exit(1) - - return arguments - - # ---------------------------------------------------------------------------- # Setting up logging # ---------------------------------------------------------------------------- @@ -604,39 +341,93 @@ def get_emoji(action: str) -> str: # ---------------------------------------------------------------------------- -# Helper function to flip XML objects into Python dictionaries +# Helper function to validate IP addresses +# ---------------------------------------------------------------------------- +def ip_callback(ip: str) -> str: + """ + Validates the input as a valid IP address. + + 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. + + Parameters + ---------- + ip : str + A string representing the IP address to be validated. + + Returns + ------- + str + The validated IP address string. + + Raises + ------ + typer.BadParameter + If the input string is not a valid IP address, a typer.BadParameter exception is raised with + an appropriate error message. + + Example + ------- + @app.command() + def main(ip_address: str = typer.Argument(..., callback=ip_callback)): + # ip_address will be a validated IP address here. + + 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. + """ + + try: + ipaddress.ip_address(ip) + return ip + + except ValueError as err: + raise typer.BadParameter("Please enter a valid IP address.") from err + + +# ---------------------------------------------------------------------------- +# Helper function to convert XML objects into Python dictionaries # ---------------------------------------------------------------------------- def xml_to_dict(xml_object: ET.Element) -> dict: """ - Converts an XML object to a Python dictionary for easy manipulation and access. + Converts an XML object to a Python dictionary for easier manipulation and access. - This function uses the 'xmltodict' library to convert an XML object into a Python dictionary. - The conversion preserves the XML tree structure, representing elements as keys and their contents - as values in the dictionary. This utility is especially useful for processing and handling XML data - in Python, facilitating access to XML elements and attributes in a Pythonic manner. + This function employs the 'xmltodict' library to transform an XML object into a Python dictionary. + The conversion process maintains the hierarchical structure of the XML, mapping elements to keys and + their contents to corresponding values in the dictionary. This approach is particularly advantageous + for handling XML data within Python, as it allows for straightforward access to XML elements and + attributes in a manner consistent with Python's data access patterns. Parameters ---------- xml_object : ET.Element - The XML object to be converted. It should be an instance of ElementTree.Element, typically obtained - from parsing XML data using the ElementTree API. + The XML object to be converted. This object should be an instance of ElementTree.Element, + usually obtained from parsing XML data using the ElementTree API. Returns ------- dict - A dictionary representation of the provided XML object. The dictionary's structure mirrors the XML's - structure, with tags as keys and their textual content as values. + A dictionary representing the XML object. The structure of this dictionary mirrors that of + the original XML, with element tags as keys and their text content or attributes as values. Example ------- - Converting an XML object to a dictionary: + Example of converting an XML object to a dictionary: >>> xml_data = ET.Element('root', attrib={'id': '1'}) >>> sub_element = ET.SubElement(xml_data, 'child') >>> sub_element.text = 'content' >>> xml_dict = xml_to_dict(xml_data) >>> print(xml_dict) {'root': {'@id': '1', 'child': 'content'}} + + Note + ----- + - This utility is independent of the specific XML schema and can be applied to any XML data. """ + xml_string = ET.tostring(xml_object) xml_dict = xmltodict.parse(xml_string) return xml_dict @@ -740,23 +531,24 @@ def check_readiness_and_log( # ---------------------------------------------------------------------------- # Setting up connection to the Firewall appliance # ---------------------------------------------------------------------------- -def connect_to_firewall(args: dict) -> Firewall: +def connect_to_firewall( + ip_address: str, + api_username: str, + api_password: str, +) -> Firewall: """ Establishes a connection to a Firewall appliance using provided credentials. - This function attempts to connect to a Firewall appliance, which can be authenticated either using an - API key or a combination of a username and password. It ensures that the target device is indeed a - Firewall and not a Panorama appliance. On successful connection, it returns a Firewall object. If the - connection fails or if the target device is a Panorama appliance, the script logs an error and terminates. + This function attempts to connect to a Firewall appliance using a combination of a username + and password. It ensures that the target device is indeed a Firewall and not a Panorama appliance. + On successful connection, it returns a Firewall object. If the connection fails or if the target + device is identified as a Panorama appliance, the script logs an error and terminates. Parameters ---------- - args : dict - A dictionary of arguments required for establishing the connection. Expected keys are: - - 'api_key': The API key for authentication (optional if username and password are provided). - - 'hostname': The hostname or IP address of the Firewall appliance. - - 'pan_username': Username for authentication (required if API key is not provided). - - 'pan_password': Password for authentication (required if API key is not provided). + - 'ip_address': The IP address of the Firewall appliance. + - 'api_username': Username for authentication. + - 'api_password': Password for authentication. Returns ------- @@ -769,29 +561,18 @@ def connect_to_firewall(args: dict) -> Firewall: - If the target device is a Panorama appliance. - If the connection to the Firewall appliance fails (e.g., due to timeout or incorrect credentials). - Examples + Example -------- - Connecting to a Firewall using an API key: - >>> connect_to_firewall({'api_key': 'apikey123', 'hostname': '192.168.0.1'}) - - - Connecting to a Firewall using username and password: - >>> connect_to_firewall({'pan_username': 'admin', 'pan_password': 'password', 'hostname': '192.168.0.1'}) + Connecting to a Firewall using a username and password: + >>> connect_to_firewall('192.168.0.1', 'admin', 'password') """ try: - # Build a connection using either an API key or username/password combination - if args["api_key"]: - target_device = PanDevice.create_from_device( - args["hostname"], - api_key=args["api_key"], - ) - else: - target_device = PanDevice.create_from_device( - args["hostname"], - args["pan_username"], - args["pan_password"], - ) + target_device = PanDevice.create_from_device( + ip_address, + api_username, + api_password, + ) if isinstance(target_device, panos.panorama.Panorama): logging.error( @@ -804,7 +585,7 @@ def connect_to_firewall(args: dict) -> Firewall: except PanConnectionTimeout: logging.error( - f"{get_emoji('error')} Connection to the firewall timed out. Please check the hostname and network connectivity." + f"{get_emoji('error')} Connection to the firewall timed out. Please check the IP address and network connectivity." ) sys.exit(1) @@ -912,24 +693,24 @@ def parse_version(version: str) -> Tuple[int, int, int, int]: # ---------------------------------------------------------------------------- def software_update_check( firewall: Firewall, - target_version: str, + version: str, ha_details: dict, ) -> bool: """ Checks if the specified PAN-OS version is available and ready for download on the firewall. This function retrieves the current PAN-OS version of the firewall and lists available versions - for upgrade. It compares these with the target version. If the target version is available and + for upgrade. It compares these with the specified version. If the specified version is available and its base image is already downloaded on the firewall, the function logs this information and - returns True. If the target version is not available or its base image is not downloaded, an - error is logged, and the function returns False. The function also verifies that the target + returns True. If the specified version is not available or its base image is not downloaded, an + error is logged, and the function returns False. The function also verifies that the specified version is a newer version compared to the current one on the firewall. Parameters ---------- firewall : Firewall The Firewall object representing the firewall to be checked. - target_version : str + version : str The desired target PAN-OS version for the upgrade. ha_details : dict High-availability details of the firewall, used to determine if HA synchronization is required. @@ -937,53 +718,53 @@ def software_update_check( Returns ------- bool - True if the target version is available and its base image is downloaded, False otherwise. + True if the specified version is available and its base image is downloaded, False otherwise. Raises ------ SystemExit - If the target version is older than or equal to the current version, indicating no upgrade is + If the specified version is older than or equal to the current version, indicating no upgrade is needed or a downgrade was attempted. Example -------- Checking if a specific PAN-OS version is available for download: - >>> firewall = Firewall(hostname='192.168.0.1', api_key='apikey') + >>> firewall = Firewall(ip_address='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 """ - # parse target version - target_major, target_minor, target_maintenance = target_version.split(".") + # parse version + major, minor, maintenance = version.split(".") - # check to see if the target version is older than the current version - determine_upgrade(firewall, target_major, target_minor, target_maintenance) + # check to see if the specified version is older than the current version + determine_upgrade(firewall, major, minor, maintenance) # retrieve available versions of PAN-OS firewall.software.check() available_versions = firewall.software.versions logging.debug(f"Available PAN-OS versions: {available_versions}") - # check to see if target version is available for upgrade - if target_version in available_versions: + # check to see if specified version is available for upgrade + if version in available_versions: logging.info( - f"{get_emoji('success')} Target PAN-OS version {target_version} is available for download" + f"{get_emoji('success')} PAN-OS version {version} is available for download" ) - # validate the target version's base image is already downloaded - if available_versions[f"{target_major}.{target_minor}.0"]["downloaded"]: + # validate the specified version's base image is already downloaded + if available_versions[f"{major}.{minor}.0"]["downloaded"]: logging.info( - f"{get_emoji('success')} Base image for {target_version} is already downloaded" + f"{get_emoji('success')} Base image for {version} is already downloaded" ) return True else: logging.error( - f"{get_emoji('error')} Base image for {target_version} is not downloaded" + f"{get_emoji('error')} Base image for {version} is not downloaded" ) return False else: logging.error( - f"{get_emoji('error')} Target PAN-OS version {target_version} is not available for download" + f"{get_emoji('error')} PAN-OS version {version} is not available for download" ) return False @@ -1014,8 +795,8 @@ def get_ha_status(firewall: Firewall) -> Tuple[str, Optional[dict]]: Example ------- Retrieving HA status of a Firewall: - >>> fw = Firewall(hostname='192.168.1.1', api_key='apikey') - >>> ha_status, ha_details = get_ha_status(fw) + >>> firewall = Firewall(ip_address='192.168.1.1', api_username='admin', api_password='password') + >>> ha_status, ha_details = get_ha_status(firewall) >>> print(ha_status) 'active/passive' >>> print(ha_details) @@ -1080,7 +861,7 @@ def software_download( Example -------- Initiating a PAN-OS version download: - >>> firewall = Firewall(hostname='192.168.0.1', api_key='apikey') + >>> firewall = Firewall(ip_address='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 @@ -1177,8 +958,8 @@ def run_assurance( ---------- firewall : Firewall The firewall instance on which to perform the operations. - hostname : str - The hostname of the firewall. + ip_address : str + The ip_address of the firewall. operation_type : str The type of operation to perform (e.g., 'readiness_check', 'state_snapshot', 'report'). actions : List[str] @@ -1199,7 +980,7 @@ def run_assurance( Example -------- Performing a state snapshot operation: - >>> firewall = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> firewall = Firewall(ip_address='192.168.1.1', 'admin', 'password') >>> run_assurance(firewall, 'firewall1', 'state_snapshot', ['arp_table', 'ip_sec_tunnels'], {}) SnapshotReport object or None @@ -1319,7 +1100,7 @@ def perform_snapshot( Example -------- Creating a network state snapshot: - >>> firewall = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> firewall = Firewall(ip_address='192.168.1.1', 'admin', 'password') >>> perform_snapshot(firewall, 'firewall1', '/path/to/snapshot.json') # Snapshot file is saved to the specified path. """ @@ -1398,7 +1179,7 @@ def perform_readiness_checks( Example -------- Conducting readiness checks: - >>> firewall = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> firewall = Firewall(ip_address='192.168.1.1', 'username', 'password') >>> perform_readiness_checks(firewall, 'firewall1', '/path/to/readiness_report.json') # Readiness report is saved to the specified path. """ @@ -1484,7 +1265,7 @@ def backup_configuration( Example -------- Backing up the firewall configuration: - >>> firewall = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> firewall = Firewall(ip_address='192.168.1.1', 'admin', 'password') >>> backup_configuration(firewall, '/path/to/config_backup.xml') # Configuration is backed up to the specified file. """ @@ -1540,8 +1321,8 @@ def perform_upgrade( hostname: str, target_version: str, ha_details: Optional[dict] = None, - max_retries: int = 3, # Maximum number of retry attempts - retry_interval: int = 60, # Time to wait between retries (in seconds) + max_retries: int = 3, + retry_interval: int = 60, ) -> None: """ Initiates and manages the upgrade process of a firewall to a specified PAN-OS version. @@ -1581,7 +1362,7 @@ def perform_upgrade( Example ------- Upgrading a firewall to a specific PAN-OS version: - >>> firewall = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> firewall = Firewall(ip_address='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. """ @@ -1665,7 +1446,7 @@ def perform_reboot(firewall: Firewall, ha_details: Optional[dict] = None) -> Non Example ------- Rebooting a firewall and ensuring its operational status: - >>> firewall = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> firewall = Firewall(ip_address='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. """ @@ -1723,7 +1504,58 @@ def perform_reboot(firewall: Firewall, ha_details: Optional[dict] = None) -> Non # ---------------------------------------------------------------------------- # Primary execution of the script # ---------------------------------------------------------------------------- -def main() -> None: +def main( + ip_address: Annotated[ + str, + typer.Option( + "--ip-address", + "-i", + help="IP address of target firewall", + prompt="IP address", + callback=ip_callback, + ), + ], + username: Annotated[ + str, + typer.Option( + "--username", + "-u", + help="Username for authentication with the Firewall appliance", + prompt="Username", + ), + ], + password: Annotated[ + str, + typer.Option( + "--password", + "-p", + help="Perform a dry run of all tests and downloads without performing the actual upgrade", + prompt="Password", + hide_input=True, + ), + ], + target_version: Annotated[ + str, + typer.Option( + "--version", + "-v", + help="Target PAN-OS version to upgrade to", + prompt="Target PAN-OS version", + ), + ], + dry_run: Annotated[ + bool, + typer.Option( + "--dry-run", + "-d", + help="Perform a dry run of all tests and downloads without performing the actual upgrade", + ), + ] = False, + log_level: Annotated[ + str, + typer.Option("--log-level", "-l", help="Set the logging output level"), + ] = "info", +): """ Main entry point for executing the firewall upgrade script. @@ -1750,7 +1582,7 @@ def main() -> None: Example Usage: ```bash - python upgrade.py --hostname 192.168.1.1 --username admin --password secret --version 10.2.7 + python upgrade.py --ip-address 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'. """ @@ -1768,12 +1600,15 @@ def main() -> None: ensure_directory_exists(os.path.join(dir, "dummy_file")) # Configure logging right after directory setup - args = parse_arguments() - configure_logging(args["log_level"]) + configure_logging(log_level) # Create our connection to the firewall logging.debug(f"{get_emoji('start')} Connecting to PAN-OS firewall...") - firewall = connect_to_firewall(args) + firewall = connect_to_firewall( + ip_address=ip_address, + api_username=username, + api_password=password, + ) logging.info(f"{get_emoji('success')} Connection to firewall established") # Refresh system information to ensure we have the latest data @@ -1795,31 +1630,29 @@ def main() -> None: logging.debug( f"{get_emoji('start')} Performing test to validate firewall's readiness..." ) - update_available = software_update_check( - firewall, args["target_version"], ha_details - ) + update_available = software_update_check(firewall, target_version, ha_details) logging.debug(f"{get_emoji('report')} Firewall readiness check complete") # gracefully exit if the firewall is not ready for an upgrade to target version if not update_available: logging.error( - f"{get_emoji('error')} Firewall is not ready for upgrade to {args['target_version']}.", + f"{get_emoji('error')} Firewall is not ready for upgrade to {target_version}.", ) sys.exit(1) # Download the target PAN-OS version logging.info( - f"{get_emoji('start')} Performing test to see if {args['target_version']} is already downloaded..." + f"{get_emoji('start')} Performing test to see if {target_version} is already downloaded..." ) - image_downloaded = software_download(firewall, args["target_version"], ha_details) + image_downloaded = software_download(firewall, target_version, ha_details) if deploy_info == "active" or deploy_info == "passive": logging.info( - f"{get_emoji('success')} {args['target_version']} has been downloaded and sync'd to HA peer." + f"{get_emoji('success')} {target_version} has been downloaded and sync'd to HA peer." ) else: logging.info( - f"{get_emoji('success')} PAN-OS version {args['target_version']} has been downloaded." + f"{get_emoji('success')} PAN-OS version {target_version} has been downloaded." ) # Begin snapshots of the network state @@ -1868,7 +1701,7 @@ def main() -> None: logging.debug(f"{get_emoji('report')} {backup_config}") # Exit execution is dry_run is True - if args["dry_run"] is True: + if dry_run is True: logging.info(f"{get_emoji('success')} Dry run complete, exiting...") logging.info(f"{get_emoji('stop')} Halting script.") sys.exit(0) @@ -1879,7 +1712,7 @@ def main() -> None: perform_upgrade( firewall=firewall, hostname=firewall_details.hostname, - target_version=args["target_version"], + target_version=target_version, ha_details=ha_details, ) @@ -1888,4 +1721,4 @@ def main() -> None: if __name__ == "__main__": - main() + typer.run(main) From e06b428dd399a0f6a56ddec97fa57b5d5517071a Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Sun, 21 Jan 2024 15:48:44 +0000 Subject: [PATCH 3/5] version bump --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f17fe92..fdaf077 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pan-os-upgrade" -version = "0.1.1" +version = "0.1.2" description = "Python script to automate the upgrade process of PAN-OS firewalls." authors = ["Calvin Remsburg "] license = "Apache 2.0" @@ -12,7 +12,7 @@ python = "^3.11" pan-os-python = "^1.11.0" panos-upgrade-assurance = "^0.3.1" pydantic = "^2.5.3" -typer = {extras = ["all"], version = "^0.9.0"} +typer = { extras = ["all"], version = "^0.9.0" } setuptools = "^69.0.3" [tool.poetry.group.dev.dependencies] From 61bbaf036fa495b49fa9ad739eb10894b43bd0da Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Sun, 21 Jan 2024 11:03:46 -0600 Subject: [PATCH 4/5] Add Release Notes to About section --- docs/about/release-notes.md | 46 +++++++++++++++++++++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 47 insertions(+) create mode 100644 docs/about/release-notes.md diff --git a/docs/about/release-notes.md b/docs/about/release-notes.md new file mode 100644 index 0000000..1805613 --- /dev/null +++ b/docs/about/release-notes.md @@ -0,0 +1,46 @@ +# Release Notes + +Welcome to the release notes for the `pan-os-upgrade` tool. This document provides a detailed record of changes, enhancements, and fixes in each version of the tool. + +## Version 0.2.0 + +**Release Date:** *<20240121>* + +### What's New + +- Allow for simply running `pan-os-upgrade` without arguments, providing an interactive prompt for missing variables +- Replaced `argparse` with `typer` for command-line argument parsing, offering a more intuitive and user-friendly CLI experience. +- Removed the option for API key authentication to streamline the authentication process. +- Removed the `.env` file lookup feature. Configuration is now exclusively handled through command-line arguments. +- Updated the `hostname` variable to `ip_address` for clarity and consistency. +- Changed `target_version` parameter name to simply `version` to make it more concise. + +### Breaking Changes + +- Scripts and automation tools using the previous `argparse` syntax or `.env` file for configuration will need to be updated to use the new `typer` CLI arguments. + +## Version 0.1.1 + +**Release Date:** *<20240119>* + +### Highlights + +- First official release of the `pan-os-upgrade` tool on PyPi. +- Made available for wide usage and distribution. + +### Notes + +- Includes all the features and functionalities as they were in the initial development build. + +## Version 0.1.0 + +**Release Date:** *<20240118>* + +### Introduction + +- Initial development build of the `pan-os-upgrade` tool. +- Laid down the foundation for the tool's functionalities and features. + +--- + +For more detailed information on each release, visit the [GitHub repository](https://github.com/cdot65/pan-os-upgrade/releases) or check the [commit history](https://github.com/cdot65/pan-os-upgrade/commits/main). diff --git a/mkdocs.yml b/mkdocs.yml index 6e5dafb..95d2866 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -29,3 +29,4 @@ nav: - About: - License: about/license.md - Contributing: about/contributing.md + - Release Notes: about/release-notes.md From 4623dc1b9e33a0d3eeb03dc985520eeb33da8e93 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Sun, 21 Jan 2024 11:04:08 -0600 Subject: [PATCH 5/5] Update pan-os-upgrade documentation with latest configuration options --- README.md | 106 ++++++++++++------------------- docs/index.md | 15 +++-- docs/user-guide/configuration.md | 90 ++++++++------------------ docs/user-guide/execution.md | 30 ++++----- 4 files changed, 86 insertions(+), 155 deletions(-) diff --git a/README.md b/README.md index 8351d4d..36721b5 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,9 @@ This guide will help you set up the `pan-os-upgrade` library in your environment The `pan-os-upgrade` library is available on PyPI and can be installed within a Python virtual environment. A virtual environment is a self-contained directory that contains a Python installation for a particular version of Python, plus a number of additional packages. -#### Using `python3 -m venv` (Recommended for Beginners) +#### Creating a Python Virtual Environment + +The steps below highlight the process for creating, activating, and installing `pan-os-upgrade` into a Python virtual environment. If you're new to Python, it may be beneficial to understand why this is such an important step, [here is a good writeup](https://realpython.com/python-virtual-environments-a-primer/) to prime yourself. 1. Create a Virtual Environment: @@ -107,76 +109,41 @@ The `pan-os-upgrade` library is available on PyPI and can be installed within a pip install pan-os-upgrade ``` -### Using Poetry (Advanced Users) - -Poetry is a tool for dependency management and packaging in Python. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you. - -1. Install Poetry: - - Follow [the official instructions](https://python-poetry.org/docs/) to install Poetry on your system. - -2. Create a New Project using Poetry: - - ```bash - poetry new panos_project - cd panos_project - ``` - -3. Add `pan-os-upgrade` as a Dependency: - - ```bash - poetry add pan-os-upgrade - ``` - - This command will create a virtual environment and install the `pan-os-upgrade` package along with its dependencies. - -4. Activate the Poetry Shell: - - To activate the virtual environment created by Poetry, use: - - ```bash - poetry shell - ``` - ### Setting Up Your Environment After setting up the virtual environment and installing the package, you can configure your environment to use the library. This can be done using command-line arguments or an .env file. -#### Option 1: Using an .env File - -Create a `.env` file in your local directory and fill it with your firewall's details: - -```env -# PAN-OS credentials - use either API key or username/password combination -PAN_USERNAME=admin -PAN_PASSWORD=paloalto123 -API_KEY= +#### Option 1: Execute `pan-os-upgrade` without Command-Line Arguments -# Hostname or IP address of the firewall -HOSTNAME=firewall1.example.com +You can simply get started by issuing `pan-os-upgrade` from your current working directory, you will be guided to input the missing requirement arguments through an interactive shell. -# Target PAN-OS version for the upgrade -TARGET_VERSION=11.0.2-h3 - -# Logging level (e.g., debug, info, warning, error, critical) -LOG_LEVEL=debug - -# Set to true for a dry run -DRY_RUN=false +```bash +$ pan-os-upgrade +IP address: 192.168.255.1 +Username: admin +Password: +Target PAN-OS version: 11.1.1 +INFO - ✅ Connection to firewall established +INFO - 📝 **021201123456** DataCenter 10.0.0.3 +INFO - 📝 Firewall HA mode: disabled +INFO - 📝 Current PAN-OS version: 11.0.2 +INFO - 📝 Target PAN-OS version: 11.1.1 +INFO - ✅ Confirmed that moving from 11.0.2 to 11.1.1 is an upgrade +...continue until completed... ``` -#### Option 2: Using Command-Line Arguments +#### Option 2: Execute `pan-os-upgrade` Using Command-Line Arguments Alternatively, you can pass these details as command-line arguments when running the script: ```bash -pan-os-upgrade --hostname 192.168.1.1 --username admin --password secret --version 10.1.0 +pan-os-upgrade --ip-address 192.168.1.1 --username admin --password secret --version 10.1.0 ``` For a dry run: ```bash -pan-os-upgrade --hostname 192.168.1.1 --username admin --password secret --version 10.1.0 --dry-run +pan-os-upgrade --ip-address 192.168.1.1 --username admin --password secret --version 10.1.0 --dry-run ```

(back to top)

@@ -184,21 +151,28 @@ pan-os-upgrade --hostname 192.168.1.1 --username admin --password secret --versi ## Usage -The script can be run from the command line with various options. It requires at least the hostname (or IP address) and the target PAN-OS version for the firewall. Authentication can be done via API key or username and password. +The script can be run from the command line with various options. + +You can view all arguments by passing the `--help` flag: + +```bash +pan-os-upgrade --help +``` ### CLI Arguments Description -* `--api-key`: API Key for authentication -* `--dry-run`: Perform a dry run of all tests and downloads without performing the actual upgrade. -* `--hostname`: Hostname or IP address of the PAN-OS firewall. -* `--log-level`: Set the logging output level (e.g., debug, info, warning). -* `--password`: Password for authentication. -* `--username`: Username for authentication. -* `--version`: Target PAN-OS version to upgrade to. +| cli argument | shorthand | type | description | +| -------------- | --------- | ---- | ----------------------------------------------------------------------------------- | +| `--dry-run` | `-d` | n/a | Perform a dry run of all tests and downloads without performing the actual upgrade. | +| `--ip-address` | `-i` | text | IP address of target firewall. | +| `--log-level` | `-l` | text | Set the logging output level (e.g., debug, info, warning). | +| `--password` | `-p` | text | Password for authentication. | +| `--username` | `-u` | text | Username for authentication. | +| `--version` | `-v` | text | Target PAN-OS version to upgrade to. |

(back to top)

-Refer to the [documentation](https://github.com/cdot65/pan-os-upgrade) for more details on usage. +Refer to the [documentation](https://cdot65.github.io/pan-os-upgrade/) for more details on usage.

(back to top)

@@ -227,7 +201,7 @@ Encountered an issue? Here are some common problems and solutions: * **Problem**: Script hangs during execution. * **Solution**: Check the firewall and network settings. Ensure the PAN-OS device is responding correctly. -For more troubleshooting tips, visit our [FAQ section](#). +For more troubleshooting tips, visit our [FAQ section](https://cdot65.github.io/pan-os-upgrade/). ## Contributing @@ -242,14 +216,14 @@ If you have a suggestion that would make this better, please fork the repo and c 4. Push to the Branch (`git push origin feature/AmazingFeature`) 5. Open a Pull Request -See [Contributing Guidelines](#) for detailed instructions. +See [Contributing Guidelines](https://cdot65.github.io/pan-os-upgrade/about/contributing/) for detailed instructions.

(back to top)

## License -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +This project is licensed under the Apache 2.0 License - see the [LICENSE](https://cdot65.github.io/pan-os-upgrade/about/license/) file for details.

(back to top)

diff --git a/docs/index.md b/docs/index.md index 0cf929c..ba3c342 100644 --- a/docs/index.md +++ b/docs/index.md @@ -11,7 +11,7 @@ hide: PaloAltoNetworks

- pan-os-upgrade, a Python CLI tool to help automate the upgrade process for PAN-OS firewalls + pan-os-upgrade, a Python CLI tool to help automate the upgrade process for PAN-OS firewalls using Typer

@@ -58,7 +58,8 @@ Python 3.8+ * pan-os-python for handling all interactions with PAN-OS firewalls. * panos-upgrade-assurance for performing Readiness Checks, Snapshots, Health Checks, and Reporting. -* Pydantic for handling the data modeling and validation. +* Pydantic for handling the data modeling and validation. +* Typer for handling the data modeling and validation. ## Installation @@ -77,9 +78,9 @@ $ pip install pan-os-upgrade

```console -$ pan-os-upgrade --hostname houston.cdot.io --version 10.2.0-h2 --username admin --password paloalto#1 +$ pan-os-upgrade --ip-address 192.168.255.211 --version 10.2.0-h2 --username admin --password paloalto#1 INFO - ✅ Connection to firewall established -INFO - 📝 007054000242050 houston 192.168.255.211 +INFO - 📝 007054000123456 houston 192.168.255.211 INFO - 📝 Firewall HA mode: disabled INFO - 📝 Current PAN-OS version: 10.2.0 INFO - 📝 Target PAN-OS version: 10.2.0-h2 @@ -89,7 +90,7 @@ INFO - ✅ Base image for 10.2.0-h2 is already downloaded INFO - 🚀 Performing test to see if 10.2.0-h2 is already downloaded... INFO - 🔍 PAN-OS version 10.2.0-h2 is not on the firewall INFO - 🚀 PAN-OS version 10.2.0-h2 is beginning download -INFO - Device 007054000242050 downloading version: 10.2.0-h2 +INFO - Device 007054000123456 downloading version: 10.2.0-h2 INFO - ⚙️ Downloading PAN-OS version 10.2.0-h2 - Elapsed time: 4 seconds INFO - ⚙️ Downloading PAN-OS version 10.2.0-h2 - Elapsed time: 36 seconds INFO - ⚙️ Downloading PAN-OS version 10.2.0-h2 - Elapsed time: 71 seconds @@ -108,7 +109,7 @@ INFO - 🚀 Performing backup of houston's configuration to local filesystem... INFO - 🚀 Not a dry run, continue with upgrade... INFO - 🚀 Performing upgrade on houston to version 10.2.0-h2... INFO - 🚀 Attempting upgrade houston to version 10.2.0-h2 (Attempt 1 of 3)... -INFO - Device 007054000242050 installing version: 10.2.0-h2 +INFO - Device 007054000123456 installing version: 10.2.0-h2 INFO - ✅ houston upgrade completed successfully INFO - 🚀 Rebooting the firewall... INFO - 📝 Command succeeded with no output @@ -122,7 +123,7 @@ INFO - ⚙️ Firewall is rebooting... INFO - ⚙️ Firewall is rebooting... INFO - ⚙️ Firewall is responding to requests but hasn't finished its reboot process... INFO - ⚙️ Firewall is responding to requests but hasn't finished its reboot process... -INFO - ✅ Firewall upgraded and rebooted in 343 seconds" +INFO - ✅ Firewall upgraded and rebooted in 343 seconds ``` diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md index be3d340..64b9992 100644 --- a/docs/user-guide/configuration.md +++ b/docs/user-guide/configuration.md @@ -1,89 +1,51 @@ # Configuration Guide for pan-os-upgrade -Proper configuration is crucial for the effective use of the `pan-os-upgrade` package. There are two main methods to configure the tool: using a `.env` file or passing command-line arguments. Each method has its strengths and weaknesses, which we'll explore below. +Proper configuration is vital for the effective use of the `pan-os-upgrade` package. As of the latest update, configuration can be done primarily through command-line arguments. -## Option 1: Using a `.env` File +## Executing `pan-os-upgrade` -A `.env` file is a simple way to store configuration settings in key-value pairs. This method is advantageous for keeping your configuration organized and easily editable in one place. +### Option 1: Execute Without Command-Line Arguments -**Creating a `.env` File:** +You can start the script by simply issuing `pan-os-upgrade` from your current working directory. The interactive shell will prompt you to input the required arguments. -1. Create a `.env` file in your project's root directory. -2. Add your firewall's details to the file, as shown below: - -```env -# PAN-OS credentials - use either API key or username/password combination -PAN_USERNAME=admin -PAN_PASSWORD=paloalto123 -API_KEY= - -# Hostname or IP address of the firewall -HOSTNAME=firewall1.example.com - -# Target PAN-OS version for the upgrade -TARGET_VERSION=11.0.2-h3 - -# Logging level (e.g., debug, info, warning, error, critical) -LOG_LEVEL=debug - -# Set to true for a dry run -DRY_RUN=false +```bash +$ pan-os-upgrade +IP address: 192.168.255.1 +Username: admin +Password: +Target PAN-OS version: 11.1.1 +...output... ``` -**Pros:** - -- Centralized configuration management. -- Easy to update or modify settings. - -**Cons:** +### Option 2: Execute Using Command-Line Arguments -- Risk of committing sensitive information to a version control system if not properly ignored. -- Less flexibility for dynamic or per-run configurations. - -## Option 2: Using Command-Line Arguments - -Command-line arguments offer a direct way to pass configuration settings each time you run the script. - -**Passing Details via Command-Line:** - -- Standard Usage: +Alternatively, you can pass these details as command-line arguments: ```bash -pan-os-upgrade --hostname 192.168.1.1 --username admin --password secret --version 10.1.0 +$ pan-os-upgrade --ip-address 192.168.1.1 --username admin --password secret --version 10.1.0 ``` -- For a Dry Run: +For a dry run: ```bash -pan-os-upgrade --hostname 192.168.1.1 --username admin --password secret --version 10.1.0 --dry-run +$ pan-os-upgrade --ip-address 192.168.1.1 --username admin --password secret --version 10.1.0 --dry-run ``` -**Pros:** - -- Greater control and flexibility for each run. -- Avoids storing sensitive details in a file. - -**Cons:** - -- Risk of exposing sensitive information in console history. -- Less convenient for repeated use with the same settings. - ## CLI Arguments Description When using command-line arguments, the following options are available: -| argument | description | required | -| ------------- | ------------------------------------------------------------------------- | --------- | -| `--api-key` | API Key for authentication | required* | -| `--dry-run` | Dry run of all tests and downloads without performing the actual upgrade. | optional | -| `--hostname` | Hostname or IP address of the PAN-OS firewall. | optional | -| `--log-level` | Set the logging output level (e.g., debug, info, warning). | optional | -| `--password` | Password for authentication. | required* | -| `--username` | Username for authentication. | required* | -| `--version` | Target PAN-OS version to upgrade to. | required | +| Argument | Description | Required | +| -------------- | -------------------------------------------------------------------- | -------- | +| `--ip-address` | IP address of the target PAN-OS firewall. | Yes | +| `--username` | Username for authentication with the firewall. | Yes | +| `--password` | Password for authentication with the firewall. | Yes | +| `--version` | Target PAN-OS version to upgrade to. | Yes | +| `--dry-run` | Perform a dry run of all tests and downloads without actual upgrade. | No | +| `--log-level` | Set the logging output level (e.g., debug, info, warning). | No | -* if using a --api-key for authentication, omit the --username and --password arguments; opposite is also true. +Note: The use of an API key and `.env` file for configuration is no longer supported. ## Next Steps -After configuring `pan-os-upgrade`, you're ready to execute the upgrade process. To learn more about the execution steps and options, proceed to the [Execution Guide](execution.md). +After configuring `pan-os-upgrade`, you're ready to execute the upgrade process. For more details on execution steps and options, proceed to the [Execution Guide](execution.md). diff --git a/docs/user-guide/execution.md b/docs/user-guide/execution.md index bbf33c7..13e1a96 100644 --- a/docs/user-guide/execution.md +++ b/docs/user-guide/execution.md @@ -1,45 +1,37 @@ # Execution Guide for pan-os-upgrade -The `pan-os-upgrade` tool automates the entire process of upgrading PAN-OS firewalls. This guide will walk you through the execution steps, detailing how to use the script with different configuration options, and what to expect in terms of output and logging. +The `pan-os-upgrade` tool automates the entire process of upgrading PAN-OS firewalls. This guide will walk you through the execution steps, detailing how to use the script with the latest configuration options, and what to expect in terms of output and logging. ## Execution Options -You can execute `pan-os-upgrade` using two primary methods: either by using a `.env` file or command-line arguments. Each method provides flexibility depending on your preference or automation setup. +You can execute `pan-os-upgrade` using command-line arguments to provide configuration details directly. -### Using a `.env` File +### Using Command-Line Arguments -If you have set up a `.env` file as per the configuration guide, simply execute the script without any additional arguments: +Execute the script by passing the configuration details as command-line arguments:
```console -pan-os-upgrade +$ pan-os-upgrade --ip-address 192.168.1.1 --username admin --password secret --version 10.1.0 ```
-This command will automatically pick up the configuration details from your `.env` file. - -### Using Command-Line Arguments +Replace `192.168.1.1`, `admin`, `secret`, and `10.1.0` with your firewall's details and the desired PAN-OS version. -Alternatively, pass the configuration details directly as command-line arguments: - -
+For a dry run: ```console -pan-os-upgrade --hostname 192.168.1.1 --username admin --password secret --version 10.1.0 +$ pan-os-upgrade --ip-address 192.168.1.1 --username admin --password secret --version 10.1.0 --dry-run ``` -
- -Here, replace `192.168.1.1`, `admin`, `secret`, and `10.1.0` with your firewall's details and the desired PAN-OS version. - ## Output
```console -pan-os-upgrade --hostname 192.168.255.211 --username admin --password secret --version 10.2.0-h2 +pan-os-upgrade --ip-address 192.168.255.211 --username admin --password secret --version 10.2.0-h2 INFO - ✅ Connection to firewall established INFO - 📝 007054000123456 houston 192.168.255.211 INFO - 📝 Firewall HA mode: disabled @@ -51,7 +43,7 @@ INFO - ✅ Base image for 10.2.0-h2 is already downloaded INFO - 🚀 Performing test to see if 10.2.0-h2 is already downloaded... INFO - 🔍 PAN-OS version 10.2.0-h2 is not on the firewall INFO - 🚀 PAN-OS version 10.2.0-h2 is beginning download -INFO - Device 007054000242050 downloading version: 10.2.0-h2 +INFO - Device 007054000123456 downloading version: 10.2.0-h2 INFO - ⚙️ Downloading PAN-OS version 10.2.0-h2 - Elapsed time: 4 seconds INFO - ⚙️ Downloading PAN-OS version 10.2.0-h2 - Elapsed time: 36 seconds INFO - ⚙️ Downloading PAN-OS version 10.2.0-h2 - Elapsed time: 71 seconds @@ -90,6 +82,8 @@ INFO - ✅ Firewall upgraded and rebooted in 542 seconds
+This output will include detailed logs of the process, such as establishing a connection, checking versions, performing upgrades, and rebooting the firewall. + ## Assurance Functions The script performs various assurance functions like readiness checks, snapshots, and configuration backups. These are stored in the `assurance/` directory, structured as follows: