pan-scm-cli 1.3.2__tar.gz → 1.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/PKG-INFO +1 -1
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/pyproject.toml +1 -1
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/incidents.py +25 -11
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/local.py +11 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/operations.py +11 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/utils/sdk_client.py +10 -8
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/LICENSE +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/README.md +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/__init__.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/client.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/README.md +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/__init__.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/command-styling.md +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/commit.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/context.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/deployment.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/identity.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/insights.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/jobs.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/mobile_agent.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/network.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/objects.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/posture.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/security.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/commands/setup.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/main.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/tests/__init__.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/utils/__init__.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/utils/config.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/utils/context.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/utils/decorators.py +0 -0
- {pan_scm_cli-1.3.2 → pan_scm_cli-1.3.3}/src/scm_cli/utils/validators.py +0 -0
|
@@ -5,6 +5,7 @@ from the SCM Unified Incident Framework.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
+
from datetime import datetime, timezone
|
|
8
9
|
|
|
9
10
|
import typer
|
|
10
11
|
from rich.console import Console
|
|
@@ -29,6 +30,17 @@ PRODUCT_OPTION = typer.Option(None, "--product", "-p", help="Filter by product n
|
|
|
29
30
|
JSON_OPTION = typer.Option(False, "--json", "-j", help="Output as JSON")
|
|
30
31
|
|
|
31
32
|
|
|
33
|
+
def _format_epoch(epoch: int | str | None) -> str:
|
|
34
|
+
"""Convert epoch timestamp (seconds or ms) to human-readable date."""
|
|
35
|
+
if epoch is None:
|
|
36
|
+
return ""
|
|
37
|
+
if isinstance(epoch, str):
|
|
38
|
+
return epoch
|
|
39
|
+
if epoch > 1_000_000_000_000:
|
|
40
|
+
return datetime.fromtimestamp(epoch / 1000, tz=timezone.utc).strftime("%Y-%m-%d %H:%M")
|
|
41
|
+
return datetime.fromtimestamp(epoch, tz=timezone.utc).strftime("%Y-%m-%d %H:%M")
|
|
42
|
+
|
|
43
|
+
|
|
32
44
|
# =============================================================================================================================================================================================
|
|
33
45
|
# INCIDENTS COMMANDS
|
|
34
46
|
# =============================================================================================================================================================================================
|
|
@@ -64,13 +76,13 @@ def list_incidents(
|
|
|
64
76
|
typer.echo("No incidents found")
|
|
65
77
|
return
|
|
66
78
|
|
|
67
|
-
table = Table(title="Security Incidents")
|
|
68
|
-
table.add_column("ID", style="cyan", no_wrap=True)
|
|
69
|
-
table.add_column("Status", style="white")
|
|
70
|
-
table.add_column("Severity", style="white")
|
|
71
|
-
table.add_column("Product", style="white")
|
|
72
|
-
table.add_column("Title", style="dim", max_width=
|
|
73
|
-
table.add_column("Raised", style="white")
|
|
79
|
+
table = Table(title="Security Incidents", show_lines=True)
|
|
80
|
+
table.add_column("ID", style="cyan", no_wrap=True, max_width=12)
|
|
81
|
+
table.add_column("Status", style="white", no_wrap=True)
|
|
82
|
+
table.add_column("Severity", style="white", no_wrap=True)
|
|
83
|
+
table.add_column("Product", style="white", no_wrap=True)
|
|
84
|
+
table.add_column("Title", style="dim", max_width=50)
|
|
85
|
+
table.add_column("Raised", style="white", no_wrap=True)
|
|
74
86
|
|
|
75
87
|
severity_styles = {"critical": "red bold", "high": "red", "medium": "yellow", "low": "green", "informational": "dim"}
|
|
76
88
|
|
|
@@ -79,13 +91,15 @@ def list_incidents(
|
|
|
79
91
|
sev_style = severity_styles.get(sev, "white")
|
|
80
92
|
status_val = inc.get("status", "")
|
|
81
93
|
status_style = "green" if status_val == "closed" else ("yellow" if status_val == "in_progress" else "white")
|
|
94
|
+
inc_id = str(inc.get("incident_id", ""))
|
|
95
|
+
short_id = inc_id[:8] + "..." if len(inc_id) > 12 else inc_id
|
|
82
96
|
table.add_row(
|
|
83
|
-
|
|
97
|
+
short_id,
|
|
84
98
|
f"[{status_style}]{status_val}[/{status_style}]",
|
|
85
99
|
f"[{sev_style}]{sev}[/{sev_style}]",
|
|
86
100
|
str(inc.get("product", "")),
|
|
87
101
|
str(inc.get("title", "")),
|
|
88
|
-
|
|
102
|
+
_format_epoch(inc.get("raised_time")),
|
|
89
103
|
)
|
|
90
104
|
|
|
91
105
|
console.print(table)
|
|
@@ -122,8 +136,8 @@ def show_incident(
|
|
|
122
136
|
typer.echo(f"Status: {incident.get('status', '')}")
|
|
123
137
|
typer.echo(f"Severity: {incident.get('severity', '')}")
|
|
124
138
|
typer.echo(f"Product: {incident.get('product', '')}")
|
|
125
|
-
typer.echo(f"Raised: {incident.get('raised_time'
|
|
126
|
-
typer.echo(f"Updated: {incident.get('updated_time'
|
|
139
|
+
typer.echo(f"Raised: {_format_epoch(incident.get('raised_time'))}")
|
|
140
|
+
typer.echo(f"Updated: {_format_epoch(incident.get('updated_time'))}")
|
|
127
141
|
typer.echo(f"Title: {incident.get('title', '')}")
|
|
128
142
|
|
|
129
143
|
alerts = incident.get("alerts", [])
|
|
@@ -67,6 +67,17 @@ def list_versions(
|
|
|
67
67
|
|
|
68
68
|
console.print(table)
|
|
69
69
|
|
|
70
|
+
except ValueError as e:
|
|
71
|
+
if "Invalid error response format" in str(e):
|
|
72
|
+
typer.echo(
|
|
73
|
+
"Error: The Local Config API returned 404. "
|
|
74
|
+
"This API may not be available for your SCM tenant or device type. "
|
|
75
|
+
"Contact Palo Alto Networks support to verify Local Config API access.",
|
|
76
|
+
err=True,
|
|
77
|
+
)
|
|
78
|
+
else:
|
|
79
|
+
typer.echo(f"Error listing config versions: {e!s}", err=True)
|
|
80
|
+
raise typer.Exit(code=1) from e
|
|
70
81
|
except Exception as e:
|
|
71
82
|
typer.echo(f"Error listing config versions: {e!s}", err=True)
|
|
72
83
|
raise typer.Exit(code=1) from e
|
|
@@ -73,6 +73,17 @@ def _run_operation(device: str, operation: str, async_mode: bool, timeout: int)
|
|
|
73
73
|
|
|
74
74
|
console.print(table)
|
|
75
75
|
|
|
76
|
+
except ValueError as e:
|
|
77
|
+
if "Invalid error response format" in str(e):
|
|
78
|
+
typer.echo(
|
|
79
|
+
f"Error: The Operations API returned 404 for {operation}. "
|
|
80
|
+
"This API may not be available for your SCM tenant or device type. "
|
|
81
|
+
"Contact Palo Alto Networks support to verify Operations API access.",
|
|
82
|
+
err=True,
|
|
83
|
+
)
|
|
84
|
+
else:
|
|
85
|
+
typer.echo(f"Error running {operation}: {e!s}", err=True)
|
|
86
|
+
raise typer.Exit(code=1) from e
|
|
76
87
|
except Exception as e:
|
|
77
88
|
typer.echo(f"Error running {operation}: {e!s}", err=True)
|
|
78
89
|
raise typer.Exit(code=1) from e
|
|
@@ -16036,10 +16036,10 @@ class SCMClient:
|
|
|
16036
16036
|
_SERIAL_PATTERN = __import__("re").compile(r"^\d{14,15}$")
|
|
16037
16037
|
|
|
16038
16038
|
def resolve_device_serial(self, device: str) -> str:
|
|
16039
|
-
"""Resolve a device name or serial number to a serial number.
|
|
16039
|
+
"""Resolve a device name, hostname, or serial number to a serial number.
|
|
16040
16040
|
|
|
16041
16041
|
Args:
|
|
16042
|
-
device: Device name or serial number.
|
|
16042
|
+
device: Device hostname, display name, or serial number.
|
|
16043
16043
|
|
|
16044
16044
|
Returns:
|
|
16045
16045
|
str: The 14-15 digit device serial number.
|
|
@@ -16057,12 +16057,14 @@ class SCMClient:
|
|
|
16057
16057
|
return "007951000123456"
|
|
16058
16058
|
|
|
16059
16059
|
try:
|
|
16060
|
-
|
|
16061
|
-
|
|
16062
|
-
|
|
16063
|
-
|
|
16064
|
-
|
|
16065
|
-
|
|
16060
|
+
all_devices = self.client.device.list()
|
|
16061
|
+
search = device.lower()
|
|
16062
|
+
for d in all_devices:
|
|
16063
|
+
if any(search == (getattr(d, field, None) or "").lower() for field in ("hostname", "display_name", "name", "serial_number")):
|
|
16064
|
+
self.logger.info(f"Resolved '{device}' to serial {d.id}")
|
|
16065
|
+
return d.id
|
|
16066
|
+
available = [f" {d.hostname or d.display_name or d.name} ({d.id})" for d in all_devices]
|
|
16067
|
+
raise ValueError(f"Device '{device}' not found. Available devices:\n" + "\n".join(available))
|
|
16066
16068
|
except ValueError:
|
|
16067
16069
|
raise
|
|
16068
16070
|
except Exception as e:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|