From 967471a25ec0d9a35ba10b941d2fcfd4050e3ce4 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Fri, 19 Jan 2024 13:05:15 -0600 Subject: [PATCH 1/7] update docstrings to prepare for sphinx website generation --- pan_os_upgrade/upgrade.py | 783 +++++++++++++++++++++++++------------- 1 file changed, 515 insertions(+), 268 deletions(-) diff --git a/pan_os_upgrade/upgrade.py b/pan_os_upgrade/upgrade.py index 3ded58d..432aed5 100644 --- a/pan_os_upgrade/upgrade.py +++ b/pan_os_upgrade/upgrade.py @@ -1,3 +1,38 @@ +""" +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. + +Imports: + Standard Libraries: + argparse: For parsing command-line arguments. + logging: For providing a logging interface. + os: For interacting with the operating system. + sys: For accessing system-specific parameters and functions. + time: For time-related functions. + RotatingFileHandler (logging.handlers): For handling log file rotation. + + External Libraries: + xml.etree.ElementTree (ET): For XML tree manipulation. + panos: For interacting with Palo Alto Networks devices. + PanDevice, SystemSettings (panos.base, panos.device): For base PAN-OS device operations. + PanConnectionTimeout, PanDeviceError, PanDeviceXapiError, PanURLError, PanXapiError (panos.errors): + For handling specific PAN-OS errors. + Firewall (panos.firewall): For handling firewall-specific operations. + + panos-upgrade-assurance package: + CheckFirewall, FirewallProxy (panos_upgrade_assurance): For performing checks and acting as a proxy to the firewall. + + Third-party libraries: + xmltodict: For converting XML data to Python dictionaries. + 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 logging @@ -38,6 +73,24 @@ # ---------------------------------------------------------------------------- # 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, @@ -52,26 +105,47 @@ # ---------------------------------------------------------------------------- class AssuranceOptions: """ - Configuration options for the panos-upgrade-assurance process. + A class encapsulating configuration options for the panos-upgrade-assurance process in PAN-OS appliances. - This class encapsulates various configurations used in the upgrade assurance process for PAN-OS appliances. - It includes definitions for readiness checks, state snapshots, and reports, which are crucial in the upgrade - process of PAN-OS appliances. + This class is a central repository for various configurations used in the upgrade assurance process. + It includes definitions for readiness checks, state snapshots, and report types, which are crucial + components in managing and ensuring the successful upgrade of PAN-OS appliances. Attributes ---------- READINESS_CHECKS : dict - A dictionary mapping each readiness check to its description, log level, and whether to exit on failure. - This provides a more detailed context for each check, allowing for tailored logging and error handling. + A dictionary mapping the names of readiness checks to their properties. Each property is a + dictionary containing a description of the check, the log level to use when reporting the + outcome of the check, and a flag indicating whether to exit the process upon failure of the check. + - `active_support`: Verifies if active support is available for the appliance. + - `arp_entry_exist`: Checks for a specific ARP entry in the ARP table. + - `candidate_config`: Checks for pending changes on the device. + - `certificates_requirements`: Verifies if certificates' keys meet minimum size requirements. + - ... (other checks follow a similar structure) REPORTS : list of str - A list of report types that can be generated for the PAN-OS appliance. These reports provide detailed - information on various aspects of the appliance, including ARP table, content version, IPsec tunnels, - license details, and more. + A list of strings where each string represents a type of report that can be generated + for the PAN-OS appliance. These reports provide insight into various aspects of the appliance's state. + Includes reports like 'arp_table', 'content_version', 'ip_sec_tunnels', etc. STATE_SNAPSHOTS : list of str - A list of state snapshot types to capture from the PAN-OS appliance. These snapshots record critical - data regarding the appliance's current state, such as ARP table, content version, IPsec tunnels, etc. + A list of strings where each string represents a type of state snapshot that can be captured + from the PAN-OS appliance. These snapshots record essential data about the appliance's current state, + such as 'arp_table', 'content_version', 'ip_sec_tunnels', etc. + + Examples + -------- + To access the log level for the 'active_support' readiness check: + >>> log_level = AssuranceOptions.READINESS_CHECKS['active_support']['log_level'] + >>> print(log_level) + warning + + To iterate over all report types: + >>> for report in AssuranceOptions.REPORTS: + >>> print(report) + arp_table + content_version + ... """ READINESS_CHECKS = { @@ -178,46 +252,57 @@ class AssuranceOptions: # ---------------------------------------------------------------------------- class Args(BaseModel): """ - A model representing the arguments needed for connecting to and - configuring the Firewall appliance. + Represents the command-line arguments for connecting and configuring a Firewall appliance. - This class uses Pydantic (or similar) for data validation, ensuring that - the provided data types and formats meet the expected criteria for each - field. + 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 : str, optional - API key for authentication with the Firewall appliance. - Default is None. - dry_run : bool, optional - Flag to indicate whether the script should perform a dry run. - Default is False. - hostname : str, optional - Hostname or IP address of the Firewall appliance. - Default is None. - log_level : str, optional - The logging level for the script. - Accepted values are 'debug', 'info', 'warning', 'error', and 'critical' - Default is 'info'. - password : str, optional - Password for authentication with the Firewall appliance. - Default is None. - target_version : str, optional - The target PAN-OS version to upgrade to. - Default is None. - username : str, optional - Username for authentication with the Firewall appliance. - Default is None. + 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: str = None + api_key: Optional[str] = None dry_run: bool = False - hostname: str = None + hostname: Optional[str] = None log_level: str = "info" - password: str = None - target_version: str = None - username: str = None + password: Optional[str] = None + target_version: Optional[str] = None + username: Optional[str] = None # ---------------------------------------------------------------------------- @@ -225,36 +310,39 @@ class Args(BaseModel): # ---------------------------------------------------------------------------- def load_environment_variables(file_path: str) -> None: """ - Load environment variables from a given file. + Load key-value pairs as environment variables from a specified file. - Reads a file line by line, checking for non-commented and non-empty lines. Each line is split into a key-value pair - and set as an environment variable. Lines beginning with '#' are treated as comments and ignored. + 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 file path of the environment variables file. The file should contain key-value pairs in the format KEY=VALUE. - Lines starting with '#' are treated as comments and are ignored. + 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 at the given file_path does not exist, this error is raised. + If the file specified by 'file_path' does not exist, a FileNotFoundError is raised. - Example - ------- - Given a file named '.env' with the following content: - ``` - # PAN-OS credentials if using an API key, leave user and password blank - PAN_USERNAME=admin - PAN_PASSWORD=password123 - API_KEY= - HOSTNAME=panorama.example.com - TARGET_VERSION= - LOG_LEVEL=debug - ``` - Calling `load_environment_variables('.env')` will set the environment variables - PAN_USERNAME, PAN_PASSWORD, API_KEY, HOSTNAME, TARGET_VERSION, and LOG_LEVEL. + 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: @@ -270,27 +358,34 @@ def load_environment_variables(file_path: str) -> None: # ---------------------------------------------------------------------------- def parse_arguments() -> Args: """ - Parse command-line arguments for interacting with a Firewall appliance. + Parses command-line arguments for configuring the Firewall appliance interaction. - Sets up an argument parser for the script, defining command-line arguments for configuration. - Supports log level, hostname, username, password, API key, and target PAN-OS version. - If necessary arguments are not provided, attempts to load them from a `.env` file. + 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. - Ensures mutual exclusivity between API key and username/password combinations. If neither - CLI arguments nor .env file configurations provide the necessary information, the script - exits and displays an error. + 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 model class populated with the parsed arguments or environment - variables. Contains fields for API key, hostname, log level, username, target version, and password. + 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 the hostname or target version is not provided either as CLI arguments or in the .env file, - or if neither the API key nor both username and password are provided. + - 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") @@ -407,24 +502,24 @@ def parse_arguments() -> Args: # ---------------------------------------------------------------------------- def configure_logging(level: str) -> None: """ - Configure the logging for the script. + Configures the logging system for the script with a specified logging level. - Sets up logging with a specified level. It initializes a logger, sets its level based on the input, - and adds two handlers: one for console output and another for file output. The file output is managed - with a RotatingFileHandler, which keeps up to three backups and a maximum file size of 1MB each. + This function sets up the global logging configuration for the script. It initializes a logger, + sets the logging level based on the input, and adds two handlers: one for console output and + another for file output. File logging uses a RotatingFileHandler to manage log file size and backups. Parameters ---------- level : str - A string representing the desired logging level. Valid values are defined in the LOGGING_LEVELS - dictionary and include 'debug', 'info', 'warning', 'error', and 'critical'. The input is - case-insensitive. If an invalid level is provided, it defaults to 'info'. + The logging level to be set for the logger. Valid options are 'debug', 'info', 'warning', + 'error', and 'critical', as defined in the LOGGING_LEVELS dictionary. The function handles the + input case-insensitively and defaults to 'info' if an invalid level is provided. Notes ----- - The logging configuration includes: - - A console handler that logs messages to the standard output. - - A file handler that logs messages to 'logs/upgrade.log', with log rotation. + - Console Handler: Logs messages to the standard output. + - File Handler: Logs messages to 'logs/upgrade.log'. The log file is rotated when it reaches 1MB, + with up to three backup files being kept. """ logging_level = getattr(logging, level.upper(), None) @@ -465,21 +560,23 @@ def configure_logging(level: str) -> None: def get_emoji(action: str) -> str: """ - Retrieve the corresponding emoji for a given action. + Retrieves the emoji character associated with a given action keyword. - Maps a specified action string to its corresponding emoji character based on a predefined set - of mappings. If the action does not have an associated emoji in the map, an empty string is returned. + This function maps a specified action keyword to its corresponding emoji character. The mapping + is defined in a predefined dictionary, 'emoji_map'. If the specified action is not recognized, + the function returns an empty string. Parameters ---------- action : str - The action keyword for which the corresponding emoji is desired. Possible actions include - 'success', 'warning', 'error', 'working', 'report', 'search', 'save', 'stop', and 'start'. + The action keyword for which the emoji is desired. Supported actions include 'success', + 'warning', 'error', 'working', 'report', 'search', 'save', 'stop', and 'start'. Returns ------- str - The emoji character associated with the given action, or an empty string if the action is not recognized. + The emoji character corresponding to the given action. Returns an empty string if the + action is not recognized in the emoji_map. Examples -------- @@ -489,8 +586,8 @@ def get_emoji(action: str) -> str: >>> get_emoji('error') 'โŒ' - >>> get_emoji('unknown') - '' + >>> get_emoji('start') + '๐Ÿš€' """ emoji_map = { "success": "โœ…", @@ -509,25 +606,36 @@ def get_emoji(action: str) -> str: # ---------------------------------------------------------------------------- # Helper function to flip XML objects into Python dictionaries # ---------------------------------------------------------------------------- -def xml_to_dict(xml_object) -> dict: +def xml_to_dict(xml_object: ET.Element) -> dict: """ - Convert an XML object into a Python dictionary. + Converts an XML object to a Python dictionary for easy manipulation and access. - This function takes an XML object, typically obtained from parsing XML data, and converts it into a Python dictionary - for easier access and manipulation. The conversion is done using the xmltodict library, which transforms the XML tree - structure into a dictionary format, maintaining elements as keys and their contents as values. This is particularly useful - for processing and interacting with XML data in a more Pythonic way. + 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. Parameters ---------- xml_object : ET.Element - An XML object to convert into a Python dictionary. This is typically an ElementTree 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. Returns ------- dict - A Python dictionary representation of the XML object. The structure of the dictionary corresponds to the structure - of the XML, with tags as keys and their contents as values. + 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. + + Example + ------- + 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'}} """ xml_string = ET.tostring(xml_object) xml_dict = xmltodict.parse(xml_string) @@ -539,14 +647,24 @@ def xml_to_dict(xml_object) -> dict: # ---------------------------------------------------------------------------- def ensure_directory_exists(file_path: str): """ - Ensure that the directory for the given file path exists. + Ensures the existence of the directory for a specified file path, creating it if necessary. - Creates the directory (and any necessary parent directories) if it does not already exist. + This function checks if the directory for a given file path exists. If it does not exist, the function + creates the directory along with any necessary parent directories. This is particularly useful for + ensuring that the file system is prepared for file operations that require specific directory structures. Parameters ---------- file_path : str - The file path for which to ensure the directory exists. + The file path whose directory needs to be verified and potentially created. The function extracts + the directory part of the file path to check its existence. + + Example + ------- + Ensuring a directory exists for a file path: + >>> file_path = '/path/to/directory/file.txt' + >>> ensure_directory_exists(file_path) + # If '/path/to/directory/' does not exist, it is created. """ directory = os.path.dirname(file_path) if not os.path.exists(directory): @@ -562,29 +680,38 @@ def check_readiness_and_log( test_info: dict, ): """ - Check readiness test results and log the outcome. + Evaluates and logs the results of a specified readiness test. - This function evaluates the outcome of a specific readiness test based on its result. - It logs the test outcome using different log levels (info, warning, error) depending - on the test's importance and its result. In cases where the test result is critical and - the test fails, the script may exit. + This function assesses the outcome of a particular readiness test by examining its result. + It logs the outcome using varying log levels (info, warning, error), determined by the + test's importance and its result. If a test is marked as critical and fails, the script + may terminate execution. Parameters ---------- result : dict - A dictionary containing the results of readiness tests. Each key is a test name, and - the value is another dictionary with 'state' and 'reason' keys. + A dictionary where each key corresponds to a readiness test name. The value is another dictionary + containing two keys: 'state' (a boolean indicating the test's success or failure) and 'reason' + (a string explaining the outcome). + test_name : str - The name of the test to check the result for. + The name of the test to evaluate. This name should correspond to a key in the 'result' dictionary. + test_info : dict - A dictionary containing information about the test, including a description, the log level, - and whether the script should exit on failure. + Information about the test, including its description, log level (info, warning, error), and a flag + indicating whether to exit the script upon test failure (exit_on_failure). Notes ----- - The function uses the `get_emoji` helper to add relevant emojis to the log messages for - better visual distinction of the test outcomes. The test result is considered a pass if - the 'state' is True; otherwise, it's a fail or skip, depending on the 'log_level'. + - The function utilizes the `get_emoji` helper function to add appropriate emojis to log messages, + enhancing readability and user experience. + - If 'state' in the test result is True, the test is logged as passed. Otherwise, it is either + logged as failed or skipped, based on the specified log level in 'test_info'. + + Raises + ------ + SystemExit + If a critical test (marked with "exit_on_failure": True) fails, the script will raise SystemExit. """ test_result = result.get( test_name, {"state": False, "reason": "Test not performed"} @@ -615,31 +742,42 @@ def check_readiness_and_log( # ---------------------------------------------------------------------------- def connect_to_firewall(args: dict) -> Firewall: """ - Establish a connection to the Firewall appliance. - - Connects to a Firewall appliance using credentials provided in 'args'. The connection - can be established either using an API key or a combination of username and password. - This function ensures the target device is a Firewall and not a Panorama appliance. + Establishes a connection to a Firewall appliance using provided credentials. - If the connection is successful, it returns an instance of the Firewall class. If the - target device is a Panorama appliance or if the connection fails, the script logs an - error message and exits. + 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. Parameters ---------- args : dict - A dictionary containing the arguments for connecting to the Firewall appliance. - Keys should include 'api_key', 'hostname', 'pan_username', and 'pan_password'. + 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). Returns ------- Firewall - An instance of the Firewall class representing the connection to the Firewall appliance. + An instance of the Firewall class representing the established connection to the Firewall appliance. Raises ------ SystemExit - If the target device is a Panorama appliance or if the required credentials are not provided. + - 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 + -------- + 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'}) + """ try: # Build a connection using either an API key or username/password combination @@ -689,31 +827,38 @@ def determine_upgrade( target_maintenance: Union[int, str], ) -> None: """ - Determine if an upgrade is needed based on the target and current PAN-OS versions. + Determines if an upgrade to a specified PAN-OS version is necessary. - Compares the major, minor, maintenance, and hotfix versions of the current PAN-OS on the firewall - with the specified target version. Logs the current and target versions and decides if an upgrade - is required. If the current version is lower than the target version, it logs that an upgrade is - required. If the current version is equal to or higher than the target version, it logs that no - upgrade is needed or a downgrade was attempted. + This function compares the current PAN-OS version of the firewall against a target version + specified by the major, minor, and maintenance/hotfix levels. It logs both the current and + target versions, then assesses whether an upgrade is required. The function concludes that an + upgrade is needed if the current version is lower than the target version. Conversely, if the + current version is equal to or higher than the target version, it indicates that no upgrade is + necessary or that a downgrade attempt has been made. Parameters ---------- firewall : Firewall - An instance of the Firewall class representing the firewall to be checked. + The Firewall instance whose PAN-OS version is to be compared. target_major : int - The major version of the target PAN-OS. + The major version number of the target PAN-OS. target_minor : int - The minor version of the target PAN-OS. + The minor version number of the target PAN-OS. target_maintenance : Union[int, str] - The maintenance (and optionally hotfix) version of the target PAN-OS. Can be an integer or a - string (to include hotfix information). + The maintenance version number of the target PAN-OS. Can be an integer or a string + (including hotfix information). Raises ------ SystemExit - If the current version is equal to or higher than the target version, indicating no upgrade - is needed or a downgrade was attempted. + If the current PAN-OS version is equal to or higher than the target version, suggesting + that no upgrade is needed or that a downgrade was attempted. + + Notes + ----- + - The function uses a nested `parse_version` function to convert version strings to a tuple of + integers for comparison. + - The logging includes emojis for better visual distinction of the messages. """ def parse_version(version: str) -> Tuple[int, int, int, int]: @@ -771,34 +916,41 @@ def software_update_check( ha_details: dict, ) -> bool: """ - Check if a specific PAN-OS software version is available for the firewall. + Checks if the specified PAN-OS version is available and ready for download on the firewall. - Retrieves the current PAN-OS version of the firewall and available versions for upgrade. - Logs the current version and available versions. If the target version is available and - its base image is already downloaded, it logs this information and returns True. If the - target version is not available or its base image is not downloaded, it logs an error and - returns False. Additionally, it checks if the target version is newer than the current version. + 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 + 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 + version is a newer version compared to the current one on the firewall. Parameters ---------- firewall : Firewall - An instance of the Firewall class representing the firewall to check. + The Firewall object representing the firewall to be checked. target_version : str - The target PAN-OS version to check availability for. + The desired target PAN-OS version for the upgrade. ha_details : dict - A dictionary containing high-availability details of the firewall. + High-availability details of the firewall, used to determine if HA synchronization is required. Returns ------- bool - True if the target version is available for upgrade and its base image is downloaded. - False if the target version is not available or its base image is not downloaded. + True if the target 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 needed or a downgrade was attempted. + If the target 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') + >>> 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(".") @@ -839,31 +991,40 @@ def software_update_check( # ---------------------------------------------------------------------------- # Determine if the firewall is standalone, HA, or in a cluster # ---------------------------------------------------------------------------- -def get_ha_status(firewall: Firewall) -> Tuple: +def get_ha_status(firewall: Firewall) -> Tuple[str, Optional[dict]]: """ - Determine the high-availability (HA) status of a Firewall appliance. + Determines the High-Availability (HA) status and configuration of a Firewall appliance. - Retrieves and logs the HA deployment information of the specified Firewall. This function checks - whether the Firewall is standalone, part of an HA pair, or in a cluster configuration. It also - extracts additional HA details if available. + This function checks and logs the HA deployment status of the specified Firewall. It identifies + whether the Firewall is in a standalone setup, part of an HA pair, or in a cluster configuration. + Additional details about the HA configuration are also retrieved and logged if available. Parameters ---------- firewall : Firewall - An instance of the Firewall class representing the firewall to check. + The Firewall instance whose HA status is to be determined. Returns ------- - tuple - A tuple containing two elements: - 1. A string indicating the deployment type (e.g., 'standalone', 'active/passive', 'active/active'). - 2. A dictionary with detailed HA information if available, or None otherwise. + Tuple[str, Optional[dict]] + A tuple containing: + 1. A string indicating the HA deployment type (e.g., 'standalone', 'active/passive', 'active/active'). + 2. A dictionary with detailed HA configuration information, if available; otherwise, None. Example ------- - >>> fw = Firewall(hostname='192.168.1.1', api_key='apikey') - >>> get_ha_status(fw) - ('active/passive', {'ha_details': ...}) + Retrieving HA status of a Firewall: + >>> fw = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> ha_status, ha_details = get_ha_status(fw) + >>> print(ha_status) + 'active/passive' + >>> print(ha_details) + {'ha_details': {...}} + + Notes + ----- + - The function utilizes the 'show_highavailability_state' method of the Firewall class to fetch HA details. + - The 'xml_to_dict' helper function is used to convert XML data into a more accessible dictionary format. """ logging.debug( f"{get_emoji('start')} Getting {firewall.serial} deployment information..." @@ -890,36 +1051,44 @@ def software_download( ha_details: dict, ) -> bool: """ - Initiate and monitor the download of a specific PAN-OS software version for the firewall. + Initiates and monitors the download of a specified PAN-OS software version on the firewall. - Starts the download process for the specified target PAN-OS version on the provided firewall instance. - Continuously checks and logs the download progress. If the download is successful, returns True. - If the download process encounters an error or fails, the function logs the appropriate message - and returns False. In case of exceptions during the download process, the script exits. + This function starts the download process for the given target PAN-OS version on the specified + firewall. It continually checks and logs the download's progress. If the download is successful, + it returns True. If the download process encounters errors or fails, these are logged, and the + function returns False. Exceptions during the download process lead to script termination. Parameters ---------- firewall : Firewall - An instance of the Firewall class representing the firewall on which the software is to be downloaded. + The Firewall instance on which the software is to be downloaded. target_version : str - The target PAN-OS version to be downloaded. + The PAN-OS version targeted for download. ha_details : dict - High-availability details of the firewall, used to determine if HA synchronization is required. + High-availability details of the firewall, determining if HA synchronization is needed. Returns ------- bool - True if the target version is successfully downloaded, False if the download fails or an error occurs. + True if the download is successful, False if the download fails or encounters an error. Raises ------ SystemExit - If an exception occurs during the download process or if the script encounters a critical error. + Raised if an exception occurs during the download process or if a critical error is encountered. + + Example + -------- + Initiating a PAN-OS version download: + >>> firewall = Firewall(hostname='192.168.0.1', api_key='apikey') + >>> software_download(firewall, '10.1.0', ha_details={}) + True or False depending on the success of the download Notes ----- - The function checks if the target version is already downloaded before initiating the download. - It also provides logging about the HA state if relevant HA details are provided. + - Before initiating the download, the function checks if the target version is already available on the firewall. + - It uses the 'download' method of the Firewall's software attribute to perform the download. + - The function sleeps for 30 seconds between each status check to allow time for the download to progress. """ if firewall.software.versions[target_version]["downloaded"]: @@ -996,45 +1165,49 @@ def run_assurance( config: Dict[str, Union[str, int, float, bool]], ) -> Union[SnapshotReport, ReadinessCheckReport, None]: """ - Perform operational tasks on the Firewall and return the results or generate reports. + Executes specified operational tasks on the firewall and returns the results or reports. - Handles various operational tasks on the Firewall based on 'operation_type', such as - performing readiness checks, capturing state snapshots, or generating reports. The function - operates according to the specified 'actions' and 'config'. If the operation is successful, - it returns the results or an SnapshotReport object. If an invalid operation type or action - is specified, or an error occurs, the function logs an error and returns None. + This function handles different operational tasks on the firewall based on the provided + 'operation_type'. It supports operations like performing readiness checks, capturing state + snapshots, and generating reports. The operation is executed according to the 'actions' and + 'config' specified. Successful operations return results or a report object. Invalid operations + or errors during execution result in logging an error and returning None. Parameters ---------- firewall : Firewall - An instance of the Firewall class representing the firewall to operate on. + The firewall instance on which to perform the operations. hostname : str - Hostname of the firewall. + The hostname of the firewall. operation_type : str - Type of operation to be performed, e.g., 'readiness_check', 'state_snapshot', 'report'. + The type of operation to perform (e.g., 'readiness_check', 'state_snapshot', 'report'). actions : List[str] - List of specific actions to be performed within the operation type. + A list of actions to be performed for the specified operation type. config : Dict[str, Union[str, int, float, bool]] - Configuration settings for the specified action. + Configuration settings for the specified actions. Returns ------- - Union[SnapshotReport, None] - The results of the operation as an SnapshotReport object, or None if an invalid - operation type or action is specified, or if an error occurs. + Union[SnapshotReport, ReadinessCheckReport, None] + The results of the operation as a report object, or None if the operation type or action is invalid, or an error occurs. Raises ------ SystemExit - If an invalid action is provided for the specified operation type or if an exception - occurs during the execution of the operation. + Raised if an invalid action is specified for the operation type or if an exception occurs during execution. + + Example + -------- + Performing a state snapshot operation: + >>> firewall = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> run_assurance(firewall, 'firewall1', 'state_snapshot', ['arp_table', 'ip_sec_tunnels'], {}) + SnapshotReport object or None Notes ----- - - 'readiness_check' verifies the firewall's readiness for upgrade tasks. - - 'state_snapshot' captures the current state of the firewall. - - 'report' generates a report based on the specified action. Implementation details for - report generation should be completed as per requirements. + - The 'readiness_check' operation verifies the firewall's readiness for upgrade-related tasks. + - The 'state_snapshot' operation captures the current state of the firewall. + - The 'report' operation generates a report based on the specified actions. This is pending implementation. """ # setup Firewall client proxy_firewall = FirewallProxy(firewall) @@ -1121,27 +1294,34 @@ def perform_snapshot( file_path: str, ) -> None: """ - Perform a snapshot of the network state on the specified firewall and save it to a file. + Executes a network state snapshot on the specified firewall and saves it as a JSON file. - Performs a series of network state information collections (such as arp_table, content_version, - ip_sec_tunnels, etc.) on the provided firewall instance. The collected data is then saved as a - JSON file to the specified file path. Logs the start, success, or failure of the snapshot operation. + This function collects various network state information from the firewall, such as ARP table, content version, + IPsec tunnels, etc. The collected data is then serialized into JSON format and saved to the provided file path. + It logs the beginning of the operation, its success, or any failures encountered during the snapshot creation. Parameters ---------- firewall : Firewall - An instance of the Firewall class representing the firewall to collect the network state from. + The firewall instance from which to collect the network state information. hostname : str - The hostname of the firewall. Used for logging purposes. + Hostname of the firewall, used primarily for logging purposes. file_path : str - The file path where the network state snapshot JSON will be saved. + Path to the file where the snapshot JSON will be saved. Notes ----- - - The function leverages the `run_assurance` function for collecting network state information. - - If the snapshot is successful, the JSON representation of the network state is saved to the specified file. - - If the snapshot fails, an error is logged. - - This function also ensures that the directory for the file path exists before saving. + - Utilizes the `run_assurance` function to collect the required network state information. + - Ensures the existence of the directory where the snapshot file will be saved. + - Logs a success message and the JSON representation of the snapshot if the operation is successful. + - Logs an error message if the snapshot creation fails. + + Example + -------- + Creating a network state snapshot: + >>> firewall = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> perform_snapshot(firewall, 'firewall1', '/path/to/snapshot.json') + # Snapshot file is saved to the specified path. """ logging.info( @@ -1192,28 +1372,35 @@ def perform_readiness_checks( file_path: str, ) -> None: """ - Perform and handle the results of readiness checks on a specified firewall. + Executes readiness checks on a specified firewall and saves the results as a JSON file. - Initiates various readiness checks on the target firewall to determine its state and suitability - for further operations, such as upgrades. The checks include candidate config, content version, - expired licenses, HA status, job status, disk space, NTP synchronization, Panorama connection, - and planes clock synchronization. The results are logged, and a detailed report is saved to the - specified file path. + This function initiates a series of readiness checks on the firewall to assess its state before + proceeding with operations like upgrades. The checks cover aspects like configuration status, + content version, license validity, HA status, and more. The results of these checks are logged, + and a detailed report is generated and saved to the provided file path. Parameters ---------- firewall : Firewall - An instance of the Firewall class representing the firewall to perform readiness checks on. + The firewall instance on which to perform the readiness checks. hostname : str - The hostname of the firewall on which the readiness checks are performed. + Hostname of the firewall, used primarily for logging purposes. file_path : str - The file path where the readiness check report will be saved. + Path to the file where the readiness check report JSON will be saved. Notes ----- - - If the readiness checks are successful, a ReadinessCheckReport is generated, logged, and saved. - - If the readiness checks fail, an error message is logged. - - The function uses emojis for logging to enhance readability and user experience. + - Utilizes the `run_assurance` function to perform the readiness checks. + - Ensures the existence of the directory where the report file will be saved. + - Logs the outcome of the readiness checks and saves the report in JSON format. + - Logs an error message if the readiness check creation fails. + + Example + -------- + Conducting readiness checks: + >>> firewall = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> perform_readiness_checks(firewall, 'firewall1', '/path/to/readiness_report.json') + # Readiness report is saved to the specified path. """ logging.debug( @@ -1265,19 +1452,41 @@ def backup_configuration( file_path: str, ) -> bool: """ - Back up the configuration of a firewall to the local filesystem. + Backs up the current running configuration of a specified firewall to a local file. + + This function retrieves the running configuration from the firewall and saves it as an XML file + at the specified file path. It checks the validity of the retrieved XML data and logs the success + or failure of the backup process. Parameters ---------- firewall : Firewall - An instance of the Firewall class representing the firewall to operate on. + The firewall instance from which the configuration is to be backed up. file_path : str - The path where the configuration file will be saved. + The path where the configuration backup file will be saved. Returns ------- bool - True if backup is successful, False otherwise. + Returns True if the backup is successfully created, False otherwise. + + Raises + ------ + Exception + Raises an exception if any error occurs during the backup process. + + Notes + ----- + - The function verifies the XML structure of the retrieved configuration. + - Ensures the directory for the backup file exists. + - The backup file is saved in XML format. + + Example + -------- + Backing up the firewall configuration: + >>> firewall = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> backup_configuration(firewall, '/path/to/config_backup.xml') + # Configuration is backed up to the specified file. """ try: @@ -1335,29 +1544,46 @@ def perform_upgrade( retry_interval: int = 60, # Time to wait between retries (in seconds) ) -> None: """ - Perform the upgrade process for the specified firewall. + Initiates and manages the upgrade process of a firewall to a specified PAN-OS version. - Performs the upgrade of the firewall to the target PAN-OS version. This includes initiating the - upgrade, handling High Availability (HA) considerations if applicable, and rebooting the firewall. - The function logs each step and handles any errors that occur during the process. + This function attempts to upgrade the firewall to the given PAN-OS version, handling potential issues + and retrying if necessary. It deals with High Availability (HA) considerations and ensures that the + upgrade process is robust against temporary failures or busy states. The function logs each step of the + process and exits the script if critical errors occur. Parameters ---------- firewall : Firewall - An instance of the Firewall class representing the firewall to be upgraded. + The firewall instance to be upgraded. hostname : str - The hostname of the firewall. Used for logging purposes. + The hostname of the firewall, used for logging purposes. target_version : str - The target PAN-OS version to upgrade the firewall to. - ha_details : Optional[dict] - High Availability details of the firewall, if applicable. + The target PAN-OS version for the upgrade. + ha_details : Optional[dict], optional + High Availability details of the firewall, by default None. + max_retries : int, optional + The maximum number of retry attempts for the upgrade, by default 3. + retry_interval : int, optional + The interval (in seconds) to wait between retry attempts, by default 60. Raises ------ SystemExit - - If the upgrade job fails. - - If HA synchronization fails or times out. - - If the firewall reboot process fails or times out. + Exits the script if the upgrade job fails, if HA synchronization issues occur, + or if critical errors are encountered during the upgrade process. + + Notes + ----- + - The function handles retries based on the 'max_retries' and 'retry_interval' parameters. + - In case of 'software manager is currently in use' errors, retries are attempted. + - Critical errors during the upgrade process lead to script termination. + + Example + ------- + Upgrading a firewall to a specific PAN-OS version: + >>> firewall = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> 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. """ logging.info( @@ -1411,23 +1637,37 @@ def perform_upgrade( # ---------------------------------------------------------------------------- def perform_reboot(firewall: Firewall, ha_details: Optional[dict] = None) -> None: """ - Perform the reboot process for the specified firewall. + Initiates and manages the reboot process for a specified firewall appliance. - Initiates the reboot of the firewall and waits until it comes back online. If the firewall is part - of a High Availability (HA) setup, it also checks for synchronization with its HA peer after rebooting. - The function logs each step and handles any errors that occur during the process. + This function triggers a reboot of the firewall and monitors its status until it comes back online. + If the firewall is part of an HA (High Availability) setup, it ensures synchronization with the HA peer + post-reboot. The process includes robust handling of various states and potential errors during reboot, + with detailed logging at each step. Parameters ---------- firewall : Firewall - An instance of the Firewall class representing the firewall to be rebooted. - ha_details : Optional[dict] - High Availability details of the firewall, if applicable. + The firewall instance to be rebooted. + ha_details : Optional[dict], optional + High Availability details for the firewall, if applicable, by default None. Raises ------ SystemExit - - If the reboot process fails or times out. + Exits the script if the reboot process encounters critical errors or timeouts. + + Notes + ----- + - The function repeatedly checks the firewall's status and HA synchronization (if applicable) post-reboot. + - Reboot completion is confirmed when the firewall is online and, in HA setups, synchronized with its peer. + - The script terminates if the firewall doesn't come online or synchronize within 20 minutes. + + Example + ------- + Rebooting a firewall and ensuring its operational status: + >>> firewall = Firewall(hostname='192.168.1.1', api_key='apikey') + >>> perform_reboot(firewall) + # The firewall undergoes a reboot and the script monitors until it's back online. """ reboot_start_time = time.time() @@ -1485,27 +1725,34 @@ def perform_reboot(firewall: Firewall, ha_details: Optional[dict] = None) -> Non # ---------------------------------------------------------------------------- def main() -> None: """ - Main function of the script, serving as the entry point. - - Orchestrates the workflow for checking and preparing a PAN-OS firewall for an upgrade. This includes: - - Parsing command-line arguments. - - Configuring logging based on specified log level. - - Connecting to the firewall and refreshing its system information. - - Determining the firewall's deployment status (standalone, HA, cluster). - - Checking firewall readiness for the target PAN-OS version and downloading it if necessary. - - Performing pre-upgrade network state snapshots and readiness checks. - - Conducting HA peer synchronization checks and backup of current configuration. - - The function uses emojis to enhance log readability and provides detailed feedback throughout the process. - If the 'dry_run' argument is set to True, the script performs checks without initiating the actual upgrade. - - Raises - ------ - SystemExit - - If the firewall is not ready for an upgrade to the target version. - - If there are critical issues preventing the continuation of the script. - - If the HA peer state is not synchronized. - - Upon completion of a dry run. + Main entry point for executing the firewall upgrade script. + + This function orchestrates the entire process of upgrading a PAN-OS firewall. It includes various stages, + such as parsing command-line arguments, establishing a connection with the firewall, assessing readiness + for upgrade, and executing the upgrade process. The function is designed to handle both dry run and actual + upgrade scenarios, providing comprehensive logging throughout. + + Steps: + 1. Create necessary directories for logs and snapshots. + 2. Configure logging based on user-defined log level. + 3. Establish a connection to the firewall and refresh its system info. + 4. Determine firewall's deployment status and readiness for upgrade. + 5. Download required PAN-OS version if not present. + 6. Perform pre-upgrade snapshots and readiness checks. + 7. Back up current firewall configuration. + 8. Proceed with upgrade and reboot if not a dry run. + + Exits the script in cases such as: + - Firewall not ready for the intended upgrade. + - Critical issues that prevent script continuation. + - Successful completion of a dry run. + - HA peer state is not synchronized (for HA setups). + + Example Usage: + ```bash + 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'. """ # Create necessary directories From 3aaafc4362daad75e685435e5e57d85ee4f109af Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Fri, 19 Jan 2024 13:34:52 -0600 Subject: [PATCH 2/7] Add sphinx and sphinx-material dependencies --- poetry.lock | 646 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 2 + 2 files changed, 647 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 97d384f..8fc9349 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,17 @@ # This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +[[package]] +name = "alabaster" +version = "0.7.16" +description = "A light, configurable Sphinx theme" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, +] + [[package]] name = "annotated-types" version = "0.6.0" @@ -31,6 +43,43 @@ six = ">=1.12.0" astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] +[[package]] +name = "babel" +version = "2.14.0" +description = "Internationalization utilities" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "beautifulsoup4" +version = "4.12.3" +description = "Screen-scraping library" +category = "dev" +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"}, + {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"}, +] + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "black" version = "23.12.1" @@ -76,6 +125,18 @@ d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "certifi" +version = "2023.11.17" +description = "Python package for providing Mozilla's CA Bundle." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, + {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, +] + [[package]] name = "cffi" version = "1.16.0" @@ -141,6 +202,106 @@ files = [ [package.dependencies] pycparser = "*" +[[package]] +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 = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + [[package]] name = "click" version = "8.1.7" @@ -214,6 +375,18 @@ ssh = ["bcrypt (>=3.1.5)"] test = ["pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] +[[package]] +name = "css-html-js-minify" +version = "2.5.5" +description = "CSS HTML JS Minifier" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "css-html-js-minify-2.5.5.zip", hash = "sha256:4a9f11f7e0496f5284d12111f3ba4ff5ff2023d12f15d195c9c48bd97013746c"}, + {file = "css_html_js_minify-2.5.5-py2.py3-none-any.whl", hash = "sha256:3da9d35ac0db8ca648c1b543e0e801d7ca0bab9e6bfd8418fee59d5ae001727a"}, +] + [[package]] name = "decorator" version = "5.1.1" @@ -226,6 +399,18 @@ files = [ {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + [[package]] name = "executing" version = "2.0.1" @@ -258,6 +443,30 @@ mccabe = ">=0.7.0,<0.8.0" pycodestyle = ">=2.11.0,<2.12.0" pyflakes = ">=3.2.0,<3.3.0" +[[package]] +name = "idna" +version = "3.6" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, + {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + [[package]] name = "ipdb" version = "0.13.13" @@ -330,6 +539,178 @@ docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alab qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] +[[package]] +name = "jinja2" +version = "3.1.3" +description = "A very fast and expressive template engine." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "lxml" +version = "5.1.0" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:704f5572ff473a5f897745abebc6df40f22d4133c1e0a1f124e4f2bd3330ff7e"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9d3c0f8567ffe7502d969c2c1b809892dc793b5d0665f602aad19895f8d508da"}, + {file = "lxml-5.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fcfbebdb0c5d8d18b84118842f31965d59ee3e66996ac842e21f957eb76138c"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f37c6d7106a9d6f0708d4e164b707037b7380fcd0b04c5bd9cae1fb46a856fb"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2befa20a13f1a75c751f47e00929fb3433d67eb9923c2c0b364de449121f447c"}, + {file = "lxml-5.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22b7ee4c35f374e2c20337a95502057964d7e35b996b1c667b5c65c567d2252a"}, + {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bf8443781533b8d37b295016a4b53c1494fa9a03573c09ca5104550c138d5c05"}, + {file = "lxml-5.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:82bddf0e72cb2af3cbba7cec1d2fd11fda0de6be8f4492223d4a268713ef2147"}, + {file = "lxml-5.1.0-cp310-cp310-win32.whl", hash = "sha256:b66aa6357b265670bb574f050ffceefb98549c721cf28351b748be1ef9577d93"}, + {file = "lxml-5.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:4946e7f59b7b6a9e27bef34422f645e9a368cb2be11bf1ef3cafc39a1f6ba68d"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:14deca1460b4b0f6b01f1ddc9557704e8b365f55c63070463f6c18619ebf964f"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ed8c3d2cd329bf779b7ed38db176738f3f8be637bb395ce9629fc76f78afe3d4"}, + {file = "lxml-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:436a943c2900bb98123b06437cdd30580a61340fbdb7b28aaf345a459c19046a"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:acb6b2f96f60f70e7f34efe0c3ea34ca63f19ca63ce90019c6cbca6b676e81fa"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af8920ce4a55ff41167ddbc20077f5698c2e710ad3353d32a07d3264f3a2021e"}, + {file = "lxml-5.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cfced4a069003d8913408e10ca8ed092c49a7f6cefee9bb74b6b3e860683b45"}, + {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9e5ac3437746189a9b4121db2a7b86056ac8786b12e88838696899328fc44bb2"}, + {file = "lxml-5.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f4c9bda132ad108b387c33fabfea47866af87f4ea6ffb79418004f0521e63204"}, + {file = "lxml-5.1.0-cp311-cp311-win32.whl", hash = "sha256:bc64d1b1dab08f679fb89c368f4c05693f58a9faf744c4d390d7ed1d8223869b"}, + {file = "lxml-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a5ab722ae5a873d8dcee1f5f45ddd93c34210aed44ff2dc643b5025981908cda"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9aa543980ab1fbf1720969af1d99095a548ea42e00361e727c58a40832439114"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6f11b77ec0979f7e4dc5ae081325a2946f1fe424148d3945f943ceaede98adb8"}, + {file = "lxml-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a36c506e5f8aeb40680491d39ed94670487ce6614b9d27cabe45d94cd5d63e1e"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f643ffd2669ffd4b5a3e9b41c909b72b2a1d5e4915da90a77e119b8d48ce867a"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16dd953fb719f0ffc5bc067428fc9e88f599e15723a85618c45847c96f11f431"}, + {file = "lxml-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16018f7099245157564d7148165132c70adb272fb5a17c048ba70d9cc542a1a1"}, + {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82cd34f1081ae4ea2ede3d52f71b7be313756e99b4b5f829f89b12da552d3aa3"}, + {file = "lxml-5.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19a1bc898ae9f06bccb7c3e1dfd73897ecbbd2c96afe9095a6026016e5ca97b8"}, + {file = "lxml-5.1.0-cp312-cp312-win32.whl", hash = "sha256:13521a321a25c641b9ea127ef478b580b5ec82aa2e9fc076c86169d161798b01"}, + {file = "lxml-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:1ad17c20e3666c035db502c78b86e58ff6b5991906e55bdbef94977700c72623"}, + {file = "lxml-5.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:24ef5a4631c0b6cceaf2dbca21687e29725b7c4e171f33a8f8ce23c12558ded1"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d2900b7f5318bc7ad8631d3d40190b95ef2aa8cc59473b73b294e4a55e9f30f"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:601f4a75797d7a770daed8b42b97cd1bb1ba18bd51a9382077a6a247a12aa38d"}, + {file = "lxml-5.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4b68c961b5cc402cbd99cca5eb2547e46ce77260eb705f4d117fd9c3f932b95"}, + {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:afd825e30f8d1f521713a5669b63657bcfe5980a916c95855060048b88e1adb7"}, + {file = "lxml-5.1.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:262bc5f512a66b527d026518507e78c2f9c2bd9eb5c8aeeb9f0eb43fcb69dc67"}, + {file = "lxml-5.1.0-cp36-cp36m-win32.whl", hash = "sha256:e856c1c7255c739434489ec9c8aa9cdf5179785d10ff20add308b5d673bed5cd"}, + {file = "lxml-5.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:c7257171bb8d4432fe9d6fdde4d55fdbe663a63636a17f7f9aaba9bcb3153ad7"}, + {file = "lxml-5.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b9e240ae0ba96477682aa87899d94ddec1cc7926f9df29b1dd57b39e797d5ab5"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a96f02ba1bcd330807fc060ed91d1f7a20853da6dd449e5da4b09bfcc08fdcf5"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3898ae2b58eeafedfe99e542a17859017d72d7f6a63de0f04f99c2cb125936"}, + {file = "lxml-5.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61c5a7edbd7c695e54fca029ceb351fc45cd8860119a0f83e48be44e1c464862"}, + {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3aeca824b38ca78d9ee2ab82bd9883083d0492d9d17df065ba3b94e88e4d7ee6"}, + {file = "lxml-5.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8f52fe6859b9db71ee609b0c0a70fea5f1e71c3462ecf144ca800d3f434f0764"}, + {file = "lxml-5.1.0-cp37-cp37m-win32.whl", hash = "sha256:d42e3a3fc18acc88b838efded0e6ec3edf3e328a58c68fbd36a7263a874906c8"}, + {file = "lxml-5.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:eac68f96539b32fce2c9b47eb7c25bb2582bdaf1bbb360d25f564ee9e04c542b"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ae15347a88cf8af0949a9872b57a320d2605ae069bcdf047677318bc0bba45b1"}, + {file = "lxml-5.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c26aab6ea9c54d3bed716b8851c8bfc40cb249b8e9880e250d1eddde9f709bf5"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:342e95bddec3a698ac24378d61996b3ee5ba9acfeb253986002ac53c9a5f6f84"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:725e171e0b99a66ec8605ac77fa12239dbe061482ac854d25720e2294652eeaa"}, + {file = "lxml-5.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d184e0d5c918cff04cdde9dbdf9600e960161d773666958c9d7b565ccc60c45"}, + {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:98f3f020a2b736566c707c8e034945c02aa94e124c24f77ca097c446f81b01f1"}, + {file = "lxml-5.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d48fc57e7c1e3df57be5ae8614bab6d4e7b60f65c5457915c26892c41afc59e"}, + {file = "lxml-5.1.0-cp38-cp38-win32.whl", hash = "sha256:7ec465e6549ed97e9f1e5ed51c657c9ede767bc1c11552f7f4d022c4df4a977a"}, + {file = "lxml-5.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:b21b4031b53d25b0858d4e124f2f9131ffc1530431c6d1321805c90da78388d1"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:52427a7eadc98f9e62cb1368a5079ae826f94f05755d2d567d93ee1bc3ceb354"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a2a2c724d97c1eb8cf966b16ca2915566a4904b9aad2ed9a09c748ffe14f969"}, + {file = "lxml-5.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843b9c835580d52828d8f69ea4302537337a21e6b4f1ec711a52241ba4a824f3"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b99f564659cfa704a2dd82d0684207b1aadf7d02d33e54845f9fc78e06b7581"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f8b0c78e7aac24979ef09b7f50da871c2de2def043d468c4b41f512d831e912"}, + {file = "lxml-5.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bcf86dfc8ff3e992fed847c077bd875d9e0ba2fa25d859c3a0f0f76f07f0c8d"}, + {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:49a9b4af45e8b925e1cd6f3b15bbba2c81e7dba6dce170c677c9cda547411e14"}, + {file = "lxml-5.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:280f3edf15c2a967d923bcfb1f8f15337ad36f93525828b40a0f9d6c2ad24890"}, + {file = "lxml-5.1.0-cp39-cp39-win32.whl", hash = "sha256:ed7326563024b6e91fef6b6c7a1a2ff0a71b97793ac33dbbcf38f6005e51ff6e"}, + {file = "lxml-5.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:8d7b4beebb178e9183138f552238f7e6613162a42164233e2bda00cb3afac58f"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9bd0ae7cc2b85320abd5e0abad5ccee5564ed5f0cc90245d2f9a8ef330a8deae"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8c1d679df4361408b628f42b26a5d62bd3e9ba7f0c0e7969f925021554755aa"}, + {file = "lxml-5.1.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:2ad3a8ce9e8a767131061a22cd28fdffa3cd2dc193f399ff7b81777f3520e372"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:304128394c9c22b6569eba2a6d98392b56fbdfbad58f83ea702530be80d0f9df"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d74fcaf87132ffc0447b3c685a9f862ffb5b43e70ea6beec2fb8057d5d2a1fea"}, + {file = "lxml-5.1.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:8cf5877f7ed384dabfdcc37922c3191bf27e55b498fecece9fd5c2c7aaa34c33"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:877efb968c3d7eb2dad540b6cabf2f1d3c0fbf4b2d309a3c141f79c7e0061324"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f14a4fb1c1c402a22e6a341a24c1341b4a3def81b41cd354386dcb795f83897"}, + {file = "lxml-5.1.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:25663d6e99659544ee8fe1b89b1a8c0aaa5e34b103fab124b17fa958c4a324a6"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8b9f19df998761babaa7f09e6bc169294eefafd6149aaa272081cbddc7ba4ca3"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e53d7e6a98b64fe54775d23a7c669763451340c3d44ad5e3a3b48a1efbdc96f"}, + {file = "lxml-5.1.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c3cd1fc1dc7c376c54440aeaaa0dcc803d2126732ff5c6b68ccd619f2e64be4f"}, + {file = "lxml-5.1.0.tar.gz", hash = "sha256:3eea6ed6e6c918e468e693c41ef07f3c3acc310b70ddd9cc72d9ef84bc9564ca"}, +] + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=3.0.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.3" +description = "Safely add untrusted strings to HTML/XML markup." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, + {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, + {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, + {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, + {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, + {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, + {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, +] + [[package]] name = "matplotlib-inline" version = "0.1.6" @@ -736,6 +1117,47 @@ cryptography = ">=41.0.5,<42" docs = ["sphinx (!=5.2.0,!=5.2.0.post0,!=7.2.5)", "sphinx-rtd-theme"] test = ["flaky", "pretend", "pytest (>=3.0.1)"] +[[package]] +name = "python-slugify" +version = "8.0.1" +description = "A Python slugify application that also handles Unicode" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "python-slugify-8.0.1.tar.gz", hash = "sha256:ce0d46ddb668b3be82f4ed5e503dbc33dd815d83e2eb6824211310d3fb172a27"}, + {file = "python_slugify-8.0.1-py2.py3-none-any.whl", hash = "sha256:70ca6ea68fe63ecc8fa4fcf00ae651fc8a5d02d93dcd12ae6d4fc7ca46c4d395"}, +] + +[package.dependencies] +text-unidecode = ">=1.3" +Unidecode = {version = ">=1.1.1", optional = true, markers = "extra == \"unidecode\""} + +[package.extras] +unidecode = ["Unidecode (>=1.1.1)"] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + [[package]] name = "six" version = "1.16.0" @@ -748,6 +1170,187 @@ files = [ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "soupsieve" +version = "2.5" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" +optional = false +python-versions = ">=3.8" +files = [ + {file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"}, + {file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"}, +] + +[[package]] +name = "sphinx" +version = "7.2.6" +description = "Python documentation generator" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx-7.2.6-py3-none-any.whl", hash = "sha256:1e09160a40b956dc623c910118fa636da93bd3ca0b9876a7b3df90f07d691560"}, + {file = "sphinx-7.2.6.tar.gz", hash = "sha256:9a5160e1ea90688d5963ba09a2dcd8bdd526620edbb65c328728f1b2228d5ab5"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.21" +imagesize = ">=1.3" +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.14" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] +test = ["cython (>=3.0)", "filelock", "html5lib", "pytest (>=4.6)", "setuptools (>=67.0)"] + +[[package]] +name = "sphinx-material" +version = "0.0.36" +description = "Material sphinx theme" +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "sphinx_material-0.0.36-py3-none-any.whl", hash = "sha256:1d7f972cca7ebdfe135e28f18401673306d7c0d036d42c6e3d98b77394e61a60"}, + {file = "sphinx_material-0.0.36.tar.gz", hash = "sha256:eeff5f7d3dc016af32bafdf70c66e671d15c8754dbe0613dfbd629fbed912869"}, +] + +[package.dependencies] +beautifulsoup4 = "*" +css-html-js-minify = "*" +lxml = "*" +python-slugify = {version = "*", extras = ["unidecode"]} +sphinx = ">=2.0" + +[package.extras] +dev = ["black (==22.12.0)"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.8" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"}, + {file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.6" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"}, + {file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.5" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"}, + {file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.7" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"}, + {file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.10" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +category = "dev" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"}, + {file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + [[package]] name = "stack-data" version = "0.6.3" @@ -768,6 +1371,18 @@ pure-eval = "*" [package.extras] tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] +[[package]] +name = "text-unidecode" +version = "1.3" +description = "The most basic Text::Unidecode port" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93"}, + {file = "text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8"}, +] + [[package]] name = "traitlets" version = "5.14.1" @@ -796,6 +1411,35 @@ files = [ {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, ] +[[package]] +name = "unidecode" +version = "1.3.8" +description = "ASCII transliterations of Unicode text" +category = "dev" +optional = false +python-versions = ">=3.5" +files = [ + {file = "Unidecode-1.3.8-py3-none-any.whl", hash = "sha256:d130a61ce6696f8148a3bd8fe779c99adeb4b870584eeb9526584e9aa091fd39"}, + {file = "Unidecode-1.3.8.tar.gz", hash = "sha256:cfdb349d46ed3873ece4586b96aa75258726e2fa8ec21d6f00a591d98806c2f4"}, +] + +[[package]] +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 = [ + {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, + {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + [[package]] name = "wcwidth" version = "0.2.13" @@ -823,4 +1467,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "539ae41348cd84b5b5379f221b850eaac57524a824fddf23b1c6fa38db982ed0" +content-hash = "9a20171f3c87d3186a8532a5d16db59093acde7de64b4ea2dd0a5bf133cc0cfd" diff --git a/pyproject.toml b/pyproject.toml index 5f633f6..81e280b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,8 @@ pydantic = "^2.5.3" black = "^23.12.1" flake8 = "^7.0.0" ipdb = "^0.13.13" +sphinx = "^7.2.6" +sphinx-material = "^0.0.36" [tool.poetry.scripts] pan-os-upgrade = 'pan_os_upgrade.upgrade:main' From d4ea73130b1b6a49aee8e25d2c02269d1965e401 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Fri, 19 Jan 2024 13:35:19 -0600 Subject: [PATCH 3/7] Refine documentation to suggest the proper way to proceed is by using pypi package --- README.md | 179 +++++++++++++++++++++++++++++------------------------- 1 file changed, 95 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index 9885692..c39a469 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Logo

PAN-OS Automation Project

- Automating PAN-OS upgrades using Python + Streamlining Palo Alto Networks Firewall Upgrades with Python Automation
Explore the docs ยป
@@ -23,33 +23,23 @@ Request Feature

-
Table of Contents
    -
  1. - About The Project - -
  2. -
  3. - Getting Started - -
  4. +
  5. About The Project
  6. +
  7. Getting Started
  8. Usage
  9. +
  10. Output
  11. +
  12. Logging
  13. Contributing
  14. License
  15. Contact
  16. Acknowledgments
- + ## About The Project [![Project Screenshot][product-screenshot]](https://paloaltonetworks.com) @@ -66,121 +56,143 @@ Key Features:

(back to top)

-### Built With + +## Getting Started -This project is built with the following technologies: +This guide will help you set up the pan_os_upgrade library in your environment, especially focusing on users who are new to Python and virtual environments. -* [Python](https://python.org/) -* [pan-os-python SDK](https://github.com/PaloAltoNetworks/pan-os-python) -* [panos-upgrade-assurance](https://github.com/PaloAltoNetworks/pan-os-upgrade-assurance) -* [Pydantic](https://docs.pydantic.dev/latest/) -* [xmltodict](https://pypi.org/project/xmltodict/) +### Prerequisites -

(back to top)

+* Python 3.8 or newer. +* Access to a Palo Alto Networks firewall. +* An active internet connection to download the package from PyPI. - +### Installation -## Prerequisites +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. -* Python 3.x -* Access to a Palo Alto Networks firewall. -* Required Python packages: (found in [requirements.txt](https://github.com/cdot65/pan-os-upgrade/blob/main/requirements.txt) file). +#### Using `python3 -m venv` (Recommended for Beginners) -## Getting Started +1. Create a Virtual Environment: -To get started with the PAN-OS upgrade project, you need to set up your environment and install the necessary dependencies. + ```bash + python3 -m venv panos_env + ``` -### Installation + This command creates a new directory panos_env which contains a copy of the Python interpreter, the standard library, and various supporting files. -Clone the repository +2. Activate the Virtual Environment: -```bash -git clone https://github.com/cdot65/pan-os-upgrade.git -cd pan-os-upgrade -``` + On Windows: -Before running the script, ensure you have Python installed on your system. If you're new to Python, here's how you can set up a virtual environment, which allows us to install Python packages without having them conflict with our system's Python environment: + ```bash + panos_env\Scripts\activate + ``` -* Setting up a virtual environment + On macOS and Linux: -If you have Poetry on your machine, simply type `poetry install` and `poetry shell` to activate this project's virtual environment. + ```bash + source panos_env/bin/activate + ``` -If Poetry is not installed, then you can build and activate the Python virtual enviornment manually. + After activation, your command line will indicate that you are now in the virtual environment. -```bash -python3 -m venv venv -``` +3. Install pan_os_upgrade: -```bash -source venv/bin/activate # On Windows use `venv\Scripts\activate` -``` + Within the activated environment, use pip to install the package: -Once the virutal environment has been created and activated, install the required packages + ```bash + pip install pan_os_upgrade + ``` -```bash -pip install -r requirements.txt -``` +### Using Poetry (Advanced Users) -

(back to top)

+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. - -## Usage +1. Install Poetry: -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. + Follow [the official instructions](https://python-poetry.org/docs/) to install Poetry on your system. -### CLI Arguments Description +2. Create a New Project using Poetry: -* `--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. + ```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. -### Define Variables Within .env +4. Activate the Poetry Shell: -As an alternative to passing CLI arguments, which can be a security risk due to your console's history function, you can instead update the variables within the `.env` file of your project. + To activate the virtual environment created by Poetry, use: -These environment variables will be used when CLI arguments are not provided, feel free to mix and match CLI arguments and hardcoded values within the .env file. Just note that if you're using an API key for authentication, leave the username and password blank. + ```bash + poetry shell + ``` -> note: CLI arguments will take precedent of .env file +### 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 + +Update the `.env` file in your project directory and fill it with your firewall's details: ```env -# PAN-OS credientials if using an API key, leave username and password blank +# PAN-OS credentials - use either API key or username/password combination PAN_USERNAME=admin PAN_PASSWORD=paloalto123 API_KEY= -# hostname or IP address +# Hostname or IP address of the firewall HOSTNAME=firewall1.example.com -# target PAN-OS version +# Target PAN-OS version for the upgrade TARGET_VERSION=11.0.2-h3 -# manage the levels of logging of the script debug, info, warning, error, critical +# Logging level (e.g., debug, info, warning, error, critical) LOG_LEVEL=debug -# dry run will not perform the actual upgrade process -DRY_RUN= +# Set to true for a dry run +DRY_RUN=false +``` +#### Option 2: 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 ``` -Then execute your script as follows: +For a dry run: ```bash -python upgrade.py +pan-os-upgrade --hostname 192.168.1.1 --username admin --password secret --version 10.1.0 --dry-run ``` -### Dry Run +

(back to top)

-To execute a dry run (which performs checks without upgrading): + +## Usage - ```bash - python upgrade.py --hostname 192.168.1.1 --username admin --password secret --version 10.0.0 --dry-run - ``` +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. -For more details on the usage and examples, refer to the [documentation](https://cdot65.github.io/pan-os-upgrade/). +### 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.

(back to top)

@@ -188,6 +200,7 @@ Refer to the [documentation](https://github.com/cdot65/pan-os-upgrade) for more

(back to top)

+ ## Output The script generates several files containing the state of the firewall and readiness checks. These files are stored in the `assurance` directory with the following structure: @@ -196,6 +209,7 @@ The script generates several files containing the state of the firewall and read * `readiness_checks`: Contains the results of readiness checks in JSON format. * `configurations`: Contains the backup of the firewall's configuration in XML format. + ## Logging Log messages are printed to the console and saved to a rotating log file located in the `logs` directory. The log level can be set via the `--log-level` argument. @@ -208,9 +222,6 @@ Encountered an issue? Here are some common problems and solutions: * **Problem**: Script fails to connect to the PAN-OS device. * **Solution**: Check if the hostname and credentials are correct. Ensure network connectivity to the PAN-OS device. -* **Problem**: Error regarding missing dependencies. - * **Solution**: Ensure all required packages are installed using `pip install -r requirements.txt`. - * **Problem**: Script hangs during execution. * **Solution**: Check the firewall and network settings. Ensure the PAN-OS device is responding correctly. From 6132c7f0176c829387f4d474ce5a56da720b60e8 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Fri, 19 Jan 2024 15:28:37 -0600 Subject: [PATCH 4/7] Update image URLs in README.md --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c39a469..050adab 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@
- Logo + Logo

PAN-OS Automation Project

Streamlining Palo Alto Networks Firewall Upgrades with Python Automation @@ -42,8 +42,6 @@ ## About The Project -[![Project Screenshot][product-screenshot]](https://paloaltonetworks.com) - This project is a comprehensive Python-based solution for automating PAN-OS upgrades. It's designed to provide network administrators and security professionals with an efficient tool to manage upgrades, configurations, and system checks of Palo Alto Networks appliances. Key Features: @@ -54,6 +52,10 @@ Key Features: > Note: this script is targeted towards standalone and `active-passive` HA environments, no testing has been performed against `active-active` or clustered firewalls. +Example Screenshot + +![Example Screenshot](https://github.com/cdot65/pan-os-upgrade/blob/main/images/screenshot.jpg?raw=true) +

(back to top)

From 091ddf324e08efb0c587ade0ccbc07b86d1ce74e Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Fri, 19 Jan 2024 15:54:52 -0600 Subject: [PATCH 5/7] Update version number in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 81e280b..bcbaae6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pan-os-upgrade" -version = "0.1.0" +version = "0.1.1" description = "Python script to automate the upgrade process of PAN-OS firewalls." authors = ["Calvin Remsburg "] license = "Apache 2.0" From ba142ef3a69154d1e672a9e001e0558286083a94 Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Fri, 19 Jan 2024 16:15:46 -0600 Subject: [PATCH 6/7] ignore local dev files --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 5649f03..acea9ed 100644 --- a/.gitignore +++ b/.gitignore @@ -162,6 +162,6 @@ cython_debug/ # ignore .env file .env -# ignore my personal snapshots -pan_os_upgrade/assurance -pan_os_upgrade/logs +# ignore local dev +assurance +logs From c88705b37e6890c5c6657e0ac81528da5a6129bf Mon Sep 17 00:00:00 2001 From: Calvin Remsburg Date: Fri, 19 Jan 2024 16:15:57 -0600 Subject: [PATCH 7/7] correcting package name --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 050adab..8351d4d 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Example Screenshot ## Getting Started -This guide will help you set up the pan_os_upgrade library in your environment, especially focusing on users who are new to Python and virtual environments. +This guide will help you set up the `pan-os-upgrade` library in your environment, especially focusing on users who are new to Python and virtual environments. ### Prerequisites @@ -71,7 +71,7 @@ This guide will help you set up the pan_os_upgrade library in your environment, ### Installation -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. +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) @@ -99,12 +99,12 @@ The pan_os_upgrade library is available on PyPI and can be installed within a Py After activation, your command line will indicate that you are now in the virtual environment. -3. Install pan_os_upgrade: +3. Install `pan-os-upgrade`: Within the activated environment, use pip to install the package: ```bash - pip install pan_os_upgrade + pip install pan-os-upgrade ``` ### Using Poetry (Advanced Users) @@ -122,13 +122,13 @@ Poetry is a tool for dependency management and packaging in Python. It allows yo cd panos_project ``` -3. Add pan_os_upgrade as a Dependency: +3. Add `pan-os-upgrade` as a Dependency: ```bash - poetry add pan_os_upgrade + poetry add pan-os-upgrade ``` - This command will create a virtual environment and install the pan_os_upgrade package along with its dependencies. + This command will create a virtual environment and install the `pan-os-upgrade` package along with its dependencies. 4. Activate the Poetry Shell: @@ -144,7 +144,7 @@ After setting up the virtual environment and installing the package, you can con #### Option 1: Using an .env File -Update the `.env` file in your project directory and fill it with your firewall's details: +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