Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

105 support ha upgrades for firewalls and panorama using firewall subcommand #116

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ WORKDIR /app
COPY settings.yaml /app

# Install any needed packages specified in requirements.txt
# Note: The requirements.txt should contain pan-os-upgrade==1.3.7
RUN pip install --no-cache-dir pan-os-upgrade==1.3.7
# Note: The requirements.txt should contain pan-os-upgrade==1.3.8
RUN pip install --no-cache-dir pan-os-upgrade==1.3.8

# Set the locale to avoid issues with emoji rendering
ENV LANG C.UTF-8
Expand Down
8 changes: 8 additions & 0 deletions docs/about/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

Welcome to the release notes for the `pan-os-upgrade` tool. This document provides a detailed record of changes, enhancements, and fixes in each version of the tool.

## Version 1.3.8

**Release Date:** *<20240318>*

### What's New in version 1.3.8

- Added support for the `firewall` command to perform HA upgrades

## Version 1.3.7

**Release Date:** *<20240318>*
Expand Down
40 changes: 20 additions & 20 deletions pan_os_upgrade/components/upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,6 +664,26 @@ def upgrade_firewall(
logging.info(f"{get_emoji(action='report')} {hostname}: HA mode: {deploy_info}")
logging.debug(f"{get_emoji(action='report')} {hostname}: HA details: {ha_details}")

# Check to see if the firewall is ready for an upgrade
logging.debug(
f"{get_emoji(action='start')} {hostname}: Checking to see if a PAN-OS upgrade is available."
)
update_available = software_update_check(
ha_details=ha_details,
hostname=hostname,
settings_file=settings_file,
settings_file_path=settings_file_path,
target_device=firewall,
target_version=target_version,
)

# gracefully exit if the firewall is not ready for an upgrade to target version
if not update_available:
logging.error(
f"{get_emoji(action='error')} {hostname}: Not ready for upgrade to {target_version}.",
)
sys.exit(1)

# If firewall is part of HA pair, determine if it's active or passive
if ha_details:
proceed_with_upgrade, peer_firewall = handle_firewall_ha(
Expand All @@ -686,26 +706,6 @@ def upgrade_firewall(
else:
return # Exit the function without proceeding to upgrade

# Check to see if the firewall is ready for an upgrade
logging.debug(
f"{get_emoji(action='start')} {hostname}: Checking to see if a PAN-OS upgrade is available."
)
update_available = software_update_check(
ha_details=ha_details,
hostname=hostname,
settings_file=settings_file,
settings_file_path=settings_file_path,
target_device=firewall,
target_version=target_version,
)

# gracefully exit if the firewall is not ready for an upgrade to target version
if not update_available:
logging.error(
f"{get_emoji(action='error')} {hostname}: Not ready for upgrade to {target_version}.",
)
sys.exit(1)

# Download the target version
logging.info(
f"{get_emoji(action='start')} {hostname}: Performing test to see if {target_version} is already downloaded."
Expand Down
110 changes: 102 additions & 8 deletions pan_os_upgrade/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
from pan_os_upgrade.components.utilities import (
console_welcome_banner,
create_firewall_mapping,
flatten_xml_to_dict,
get_emoji,
ip_callback,
select_devices_from_table,
Expand Down Expand Up @@ -241,14 +242,107 @@ def firewall(
settings_file_path=SETTINGS_FILE_PATH,
)

# Perform upgrade
upgrade_firewall(
dry_run=dry_run,
firewall=device,
settings_file=SETTINGS_FILE,
settings_file_path=SETTINGS_FILE_PATH,
target_version=target_version,
)
firewall_objects_for_upgrade = [device]

# Determine if the targeted device is operating in an HA pair
ha_status = device.op("show high-availability state")
ha_dict = flatten_xml_to_dict(ha_status)

# If the device is in an HA pair, store the peer's information
if ha_dict["result"]["enabled"] == "yes":
# Store all peer-info details in a dictionary
peer = ha_dict["result"]["group"]["peer-info"]

# Determine the peer's IP address if the mgmt-ip is not empty
if peer["mgmt-ip"] and len(peer["mgmt-ip"]) > 0:
peer["ip"] = peer["mgmt-ip"].split("/")[0]

# If the mgmt-ip is empty, use the mgmt-ipv6 field
elif peer["mgmt-ipv6"] and len(peer["mgmt-ipv6"]) > 0:
peer["ip"] = peer["mgmt-ipv6"].split("/")[0]

# If the mgmt-ip and mgmt-ipv6 fields are both empty, use the ha1-ipaddr field
elif peer["ha1-ipaddr"] and len(peer["ha1-ipaddr"]) > 0:
peer["ip"] = peer["ha1-ipaddr"]

else:
# no mgmt-ip or mgmt-ipv6 or ha1-ipaddr found, log message and sys.exit
logging.error(
f"{get_emoji(action='error')} {hostname}: No IP address found for the peer firewall. Exiting."
)
sys.exit(1)

firewall_objects_for_upgrade.append(Firewall(peer["ip"], username, password))

# First round of upgrades, targeting all firewalls and placing active firewalls in an HA pair on a revisit list
with ThreadPoolExecutor(max_workers=2) as executor:
# Store future objects along with firewalls for reference
future_to_firewall = {
executor.submit(
upgrade_firewall,
dry_run=dry_run,
firewall=target_device,
settings_file=SETTINGS_FILE,
settings_file_path=SETTINGS_FILE_PATH,
target_devices_to_revisit=target_devices_to_revisit,
target_devices_to_revisit_lock=target_devices_to_revisit_lock,
target_version=target_version,
): target_device
for target_device in firewall_objects_for_upgrade
}

# Process completed tasks
for future in as_completed(future_to_firewall):
firewall = future_to_firewall[future]
try:
future.result()
except Exception as exc:
logging.error(
f"{get_emoji(action='error')} {hostname}: Firewall {firewall.hostname} generated an exception: {exc}"
)

# Second round of upgrades, revisiting firewalls that were active in an HA pair and had the same version as their peers
if target_devices_to_revisit:
logging.info(
f"{get_emoji(action='start')} {hostname}: Revisiting firewalls that were active in an HA pair and had the same version as their peers."
)

# Using ThreadPoolExecutor to manage threads for revisiting firewalls
threads = SETTINGS_FILE.get("concurrency.threads", 10)
logging.debug(
f"{get_emoji(action='working')} {hostname}: Using {threads} threads."
)
with ThreadPoolExecutor(max_workers=threads) as executor:
future_to_firewall = {
executor.submit(
upgrade_firewall,
dry_run=dry_run,
firewall=target_device,
settings_file=SETTINGS_FILE,
settings_file_path=SETTINGS_FILE_PATH,
target_devices_to_revisit=target_devices_to_revisit,
target_devices_to_revisit_lock=target_devices_to_revisit_lock,
target_version=target_version,
): target_device
for target_device in target_devices_to_revisit
}

# Process completed tasks
for future in as_completed(future_to_firewall):
firewall = future_to_firewall[future]
try:
future.result()
logging.info(
f"{get_emoji(action='success')} {hostname}: Completed revisiting firewalls"
)
except Exception as exc:
logging.error(
f"{get_emoji(action='error')} {hostname}: Exception while revisiting firewalls: {exc}"
)

# Clear the list after revisiting
with target_devices_to_revisit_lock:
target_devices_to_revisit.clear()


# Subcommand for upgrading Panorama
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pan-os-upgrade"
version = "1.3.7"
version = "1.3.8"
description = "Python script to automate the upgrade process of PAN-OS firewalls."
authors = ["Calvin Remsburg <[email protected]>"]
documentation = "https://cdot65.github.io/pan-os-upgrade/"
Expand Down
Loading