netconverter-cli 0.4.0__py3-none-any.whl
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.
- netconverter_cli/__init__.py +3 -0
- netconverter_cli/activity.py +83 -0
- netconverter_cli/api.py +257 -0
- netconverter_cli/backend.py +107 -0
- netconverter_cli/branding.py +79 -0
- netconverter_cli/catalog.py +162 -0
- netconverter_cli/clerk.py +602 -0
- netconverter_cli/cli.py +521 -0
- netconverter_cli/commands.py +63 -0
- netconverter_cli/connect.py +465 -0
- netconverter_cli/controller.py +331 -0
- netconverter_cli/credentials.py +94 -0
- netconverter_cli/guided.py +365 -0
- netconverter_cli/mcp_server.py +640 -0
- netconverter_cli/palette.py +16 -0
- netconverter_cli/presentation.py +254 -0
- netconverter_cli/privacy.py +105 -0
- netconverter_cli/providers.py +246 -0
- netconverter_cli/remote_mcp.py +175 -0
- netconverter_cli/setup.py +179 -0
- netconverter_cli/tui.py +607 -0
- netconverter_cli/update.py +344 -0
- netconverter_cli/workspace.py +148 -0
- netconverter_cli-0.4.0.dist-info/METADATA +167 -0
- netconverter_cli-0.4.0.dist-info/RECORD +28 -0
- netconverter_cli-0.4.0.dist-info/WHEEL +4 -0
- netconverter_cli-0.4.0.dist-info/entry_points.txt +2 -0
- netconverter_cli-0.4.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""What the connected server accepts, in one shape for both credential profiles.
|
|
2
|
+
|
|
3
|
+
``external`` credentials (an ``nck_*`` key or a Clerk sign-in) read
|
|
4
|
+
``GET /capabilities``: every vendor, source→target path and job type the full
|
|
5
|
+
API accepts. ``quick`` credentials (0.1.x Terminal Quick pilot keys) keep the
|
|
6
|
+
pilot's fixed ASA → Palo Alto surface. Menus are built from this object, never
|
|
7
|
+
from a list in the client.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
|
|
14
|
+
JOB_TYPES = ("convert", "validate", "optimize")
|
|
15
|
+
QUICK_SOURCES = ("cisco_asa", "palo_alto_set", "palo_alto_xml", "palo_alto_panorama")
|
|
16
|
+
QUICK_LIMIT_BYTES = 50_000
|
|
17
|
+
DEVICE_TYPES = ("firewall", "router", "switch")
|
|
18
|
+
|
|
19
|
+
# Names for codes a server response may not name (quick capabilities carry codes only).
|
|
20
|
+
NAMES = {
|
|
21
|
+
"cisco_asa": "Cisco ASA",
|
|
22
|
+
"palo_alto_set": "Palo Alto (Set)",
|
|
23
|
+
"palo_alto_xml": "Palo Alto (XML)",
|
|
24
|
+
"palo_alto_panorama": "Palo Alto (Panorama)",
|
|
25
|
+
}
|
|
26
|
+
# Human labels for option values shown in menus and the review card.
|
|
27
|
+
LABELS = {
|
|
28
|
+
"vr": "Virtual router (vr)",
|
|
29
|
+
"lr": "Logical router (lr)",
|
|
30
|
+
"like_for_like": "Like for like",
|
|
31
|
+
"ai_recommended": "AI recommended",
|
|
32
|
+
"convert": "Convert",
|
|
33
|
+
"validate": "Validate",
|
|
34
|
+
"optimize": "Optimize",
|
|
35
|
+
"analyze": "Validate",
|
|
36
|
+
}
|
|
37
|
+
FIELD_LABELS = {
|
|
38
|
+
"source_vendor": "Source",
|
|
39
|
+
"target_vendor": "Target",
|
|
40
|
+
"panos_target_version": "PAN-OS",
|
|
41
|
+
"routing_engine": "Routing",
|
|
42
|
+
"pipeline_mode": "Mode",
|
|
43
|
+
"remove_unused_objects": "Unused objects",
|
|
44
|
+
"panorama_device_group": "Device group",
|
|
45
|
+
"panorama_template": "Template",
|
|
46
|
+
"enable_appid": "App-ID",
|
|
47
|
+
"app_id_implementation": "App-ID mode",
|
|
48
|
+
"add_security_profiles": "Profiles",
|
|
49
|
+
"log_all_rules": "Log all rules",
|
|
50
|
+
"implicit_deny": "Explicit deny",
|
|
51
|
+
"default_profile_group": "Profile group",
|
|
52
|
+
"zone_mapping": "Zone mapping",
|
|
53
|
+
"job_type": "Job",
|
|
54
|
+
"file": "File",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def device_type_of(code: str) -> str:
|
|
59
|
+
for kind in ("router", "switch"):
|
|
60
|
+
if code.endswith("_" + kind):
|
|
61
|
+
return kind
|
|
62
|
+
return "firewall"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class Catalog:
|
|
67
|
+
profile: str = "unknown" # external | quick | unknown (not loaded yet)
|
|
68
|
+
vendors: dict = field(default_factory=dict) # code -> {name, device_type, roles, input}
|
|
69
|
+
paths: list = field(default_factory=list) # [(source, target)]
|
|
70
|
+
job_types: tuple = ()
|
|
71
|
+
limits: dict = field(default_factory=dict)
|
|
72
|
+
remaining: dict = field(default_factory=dict)
|
|
73
|
+
quick: dict = field(default_factory=dict) # pilot-only conversion settings
|
|
74
|
+
trial: bool = False
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def from_external(cls, caps: dict) -> "Catalog":
|
|
78
|
+
vendors = {}
|
|
79
|
+
for item in caps.get("vendors") or []:
|
|
80
|
+
if isinstance(item, dict) and isinstance(item.get("code"), str):
|
|
81
|
+
code = item["code"]
|
|
82
|
+
vendors[code] = {
|
|
83
|
+
"name": str(item.get("name") or NAMES.get(code) or code),
|
|
84
|
+
"device_type": item.get("device_type") or device_type_of(code),
|
|
85
|
+
"roles": list(item.get("roles") or []),
|
|
86
|
+
"input": item.get("input") or "text",
|
|
87
|
+
}
|
|
88
|
+
paths = [
|
|
89
|
+
(p["source"], p["target"]) for p in caps.get("paths") or []
|
|
90
|
+
if isinstance(p, dict) and p.get("source") in vendors and p.get("target") in vendors
|
|
91
|
+
]
|
|
92
|
+
types = tuple(t for t in caps.get("job_types") or ("convert",) if t in JOB_TYPES)
|
|
93
|
+
return cls("external", vendors, paths, types or ("convert",),
|
|
94
|
+
dict(caps.get("limits") or {}), {}, {}, bool(caps.get("trial")))
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def from_quick(cls, caps: dict) -> "Catalog":
|
|
98
|
+
targets = [t for t in caps.get("targets") or QUICK_SOURCES[1:]]
|
|
99
|
+
vendors = {code: {"name": NAMES.get(code, code), "device_type": "firewall",
|
|
100
|
+
"roles": ["source"] + (["target"] if code in targets else []),
|
|
101
|
+
"input": "text"} for code in QUICK_SOURCES}
|
|
102
|
+
paths = [("cisco_asa", t) for t in targets]
|
|
103
|
+
quick = {k: caps[k] for k in ("panos_versions", "routing_engines", "pipeline_modes") if caps.get(k)}
|
|
104
|
+
limits = {"max_config_bytes": (caps.get("limits") or {}).get("max_config_bytes") or QUICK_LIMIT_BYTES}
|
|
105
|
+
return cls("quick", vendors, paths, JOB_TYPES, limits,
|
|
106
|
+
dict(caps.get("remaining") or {}), quick)
|
|
107
|
+
|
|
108
|
+
# -- questions the menus ask ---------------------------------------------
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def loaded(self) -> bool:
|
|
112
|
+
return bool(self.vendors)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def max_bytes(self) -> int:
|
|
116
|
+
value = self.limits.get("max_config_bytes")
|
|
117
|
+
return value if isinstance(value, int) and value > 0 else QUICK_LIMIT_BYTES
|
|
118
|
+
|
|
119
|
+
def name(self, code: str | None) -> str:
|
|
120
|
+
if not code:
|
|
121
|
+
return "—"
|
|
122
|
+
return self.vendors.get(code, {}).get("name") or NAMES.get(code) or code
|
|
123
|
+
|
|
124
|
+
def sources(self, job_type: str = "convert") -> list[str]:
|
|
125
|
+
"""Sources this job type can start from, uploadable as a text file."""
|
|
126
|
+
with_paths = {s for s, _ in self.paths}
|
|
127
|
+
return [
|
|
128
|
+
code for code, v in self.vendors.items()
|
|
129
|
+
if "source" in v["roles"] and v["input"] == "text"
|
|
130
|
+
and (job_type != "convert" or code in with_paths)
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
def targets(self, source: str) -> list[str]:
|
|
134
|
+
return [t for s, t in self.paths if s == source]
|
|
135
|
+
|
|
136
|
+
def grouped(self, codes: list[str]) -> list[tuple[str, str]]:
|
|
137
|
+
"""(label, code) ordered firewall → router → switch, device type shown when ambiguous."""
|
|
138
|
+
names = [self.name(c) for c in codes]
|
|
139
|
+
out = []
|
|
140
|
+
for kind in DEVICE_TYPES:
|
|
141
|
+
for code in (c for c in codes if self.vendors.get(c, {}).get("device_type", "firewall") == kind):
|
|
142
|
+
label = self.name(code)
|
|
143
|
+
if names.count(label) > 1:
|
|
144
|
+
label += f" · {kind}"
|
|
145
|
+
out.append((label, code))
|
|
146
|
+
return out
|
|
147
|
+
|
|
148
|
+
def supports(self, job_type: str) -> bool:
|
|
149
|
+
return job_type in self.job_types
|
|
150
|
+
|
|
151
|
+
def describe(self) -> str:
|
|
152
|
+
if self.profile == "quick":
|
|
153
|
+
return "Pilot key · ASA → Palo Alto"
|
|
154
|
+
if not self.loaded:
|
|
155
|
+
return ""
|
|
156
|
+
return f"{len([v for v in self.vendors.values() if 'source' in v['roles']])} sources · {len(self.paths)} paths"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def label(value) -> str:
|
|
160
|
+
if isinstance(value, bool):
|
|
161
|
+
return "Yes" if value else "No"
|
|
162
|
+
return LABELS.get(value, str(value)) if isinstance(value, str) else str(value)
|