bt-cli 0.4.13__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.
- bt_cli/__init__.py +3 -0
- bt_cli/cli.py +830 -0
- bt_cli/commands/__init__.py +1 -0
- bt_cli/commands/configure.py +415 -0
- bt_cli/commands/learn.py +229 -0
- bt_cli/commands/quick.py +784 -0
- bt_cli/core/__init__.py +1 -0
- bt_cli/core/auth.py +213 -0
- bt_cli/core/client.py +313 -0
- bt_cli/core/config.py +393 -0
- bt_cli/core/config_file.py +420 -0
- bt_cli/core/csv_utils.py +91 -0
- bt_cli/core/errors.py +247 -0
- bt_cli/core/output.py +205 -0
- bt_cli/core/prompts.py +87 -0
- bt_cli/core/rest_debug.py +221 -0
- bt_cli/data/CLAUDE.md +94 -0
- bt_cli/data/__init__.py +0 -0
- bt_cli/data/skills/bt/SKILL.md +108 -0
- bt_cli/data/skills/entitle/SKILL.md +170 -0
- bt_cli/data/skills/epmw/SKILL.md +144 -0
- bt_cli/data/skills/pra/SKILL.md +150 -0
- bt_cli/data/skills/pws/SKILL.md +198 -0
- bt_cli/entitle/__init__.py +1 -0
- bt_cli/entitle/client/__init__.py +5 -0
- bt_cli/entitle/client/base.py +443 -0
- bt_cli/entitle/commands/__init__.py +24 -0
- bt_cli/entitle/commands/accounts.py +53 -0
- bt_cli/entitle/commands/applications.py +39 -0
- bt_cli/entitle/commands/auth.py +68 -0
- bt_cli/entitle/commands/bundles.py +218 -0
- bt_cli/entitle/commands/integrations.py +60 -0
- bt_cli/entitle/commands/permissions.py +70 -0
- bt_cli/entitle/commands/policies.py +97 -0
- bt_cli/entitle/commands/resources.py +131 -0
- bt_cli/entitle/commands/roles.py +74 -0
- bt_cli/entitle/commands/users.py +123 -0
- bt_cli/entitle/commands/workflows.py +187 -0
- bt_cli/entitle/models/__init__.py +31 -0
- bt_cli/entitle/models/bundle.py +28 -0
- bt_cli/entitle/models/common.py +37 -0
- bt_cli/entitle/models/integration.py +30 -0
- bt_cli/entitle/models/permission.py +27 -0
- bt_cli/entitle/models/policy.py +25 -0
- bt_cli/entitle/models/resource.py +29 -0
- bt_cli/entitle/models/role.py +28 -0
- bt_cli/entitle/models/user.py +24 -0
- bt_cli/entitle/models/workflow.py +55 -0
- bt_cli/epmw/__init__.py +1 -0
- bt_cli/epmw/client/__init__.py +5 -0
- bt_cli/epmw/client/base.py +848 -0
- bt_cli/epmw/commands/__init__.py +33 -0
- bt_cli/epmw/commands/audits.py +250 -0
- bt_cli/epmw/commands/auth.py +55 -0
- bt_cli/epmw/commands/computers.py +140 -0
- bt_cli/epmw/commands/events.py +233 -0
- bt_cli/epmw/commands/groups.py +215 -0
- bt_cli/epmw/commands/policies.py +673 -0
- bt_cli/epmw/commands/quick.py +348 -0
- bt_cli/epmw/commands/requests.py +224 -0
- bt_cli/epmw/commands/roles.py +78 -0
- bt_cli/epmw/commands/tasks.py +38 -0
- bt_cli/epmw/commands/users.py +219 -0
- bt_cli/epmw/models/__init__.py +1 -0
- bt_cli/pra/__init__.py +1 -0
- bt_cli/pra/client/__init__.py +5 -0
- bt_cli/pra/client/base.py +618 -0
- bt_cli/pra/commands/__init__.py +30 -0
- bt_cli/pra/commands/auth.py +55 -0
- bt_cli/pra/commands/import_export.py +442 -0
- bt_cli/pra/commands/jump_clients.py +139 -0
- bt_cli/pra/commands/jump_groups.py +146 -0
- bt_cli/pra/commands/jump_items.py +638 -0
- bt_cli/pra/commands/jumpoints.py +95 -0
- bt_cli/pra/commands/policies.py +197 -0
- bt_cli/pra/commands/quick.py +470 -0
- bt_cli/pra/commands/teams.py +81 -0
- bt_cli/pra/commands/users.py +87 -0
- bt_cli/pra/commands/vault.py +564 -0
- bt_cli/pra/models/__init__.py +27 -0
- bt_cli/pra/models/common.py +12 -0
- bt_cli/pra/models/jump_client.py +25 -0
- bt_cli/pra/models/jump_group.py +15 -0
- bt_cli/pra/models/jump_item.py +72 -0
- bt_cli/pra/models/jumpoint.py +19 -0
- bt_cli/pra/models/team.py +14 -0
- bt_cli/pra/models/user.py +17 -0
- bt_cli/pra/models/vault.py +45 -0
- bt_cli/pws/__init__.py +1 -0
- bt_cli/pws/client/__init__.py +5 -0
- bt_cli/pws/client/base.py +356 -0
- bt_cli/pws/client/beyondinsight.py +869 -0
- bt_cli/pws/client/passwordsafe.py +1786 -0
- bt_cli/pws/commands/__init__.py +33 -0
- bt_cli/pws/commands/accounts.py +372 -0
- bt_cli/pws/commands/assets.py +311 -0
- bt_cli/pws/commands/auth.py +166 -0
- bt_cli/pws/commands/clouds.py +221 -0
- bt_cli/pws/commands/config.py +344 -0
- bt_cli/pws/commands/credentials.py +347 -0
- bt_cli/pws/commands/databases.py +306 -0
- bt_cli/pws/commands/directories.py +199 -0
- bt_cli/pws/commands/functional.py +298 -0
- bt_cli/pws/commands/import_export.py +452 -0
- bt_cli/pws/commands/platforms.py +118 -0
- bt_cli/pws/commands/quick.py +1646 -0
- bt_cli/pws/commands/search.py +256 -0
- bt_cli/pws/commands/secrets.py +1343 -0
- bt_cli/pws/commands/systems.py +389 -0
- bt_cli/pws/commands/users.py +415 -0
- bt_cli/pws/commands/workgroups.py +166 -0
- bt_cli/pws/config.py +18 -0
- bt_cli/pws/models/__init__.py +19 -0
- bt_cli/pws/models/account.py +186 -0
- bt_cli/pws/models/asset.py +102 -0
- bt_cli/pws/models/common.py +132 -0
- bt_cli/pws/models/system.py +121 -0
- bt_cli-0.4.13.dist-info/METADATA +417 -0
- bt_cli-0.4.13.dist-info/RECORD +121 -0
- bt_cli-0.4.13.dist-info/WHEEL +4 -0
- bt_cli-0.4.13.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
"""CLI commands for managing managed systems."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import typer
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from ...core.output import print_api_error
|
|
12
|
+
from ..client.base import get_client
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(no_args_is_help=True, help="Manage managed systems in Password Safe")
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def print_systems_table(systems: list[dict], title: str = "Managed Systems") -> None:
|
|
19
|
+
"""Print systems in a formatted table."""
|
|
20
|
+
table = Table(title=title)
|
|
21
|
+
table.add_column("ID", style="cyan", no_wrap=True)
|
|
22
|
+
table.add_column("Name", style="green")
|
|
23
|
+
table.add_column("Platform", style="yellow")
|
|
24
|
+
table.add_column("Workgroup", style="magenta")
|
|
25
|
+
table.add_column("Port", style="blue")
|
|
26
|
+
|
|
27
|
+
for system in systems:
|
|
28
|
+
table.add_row(
|
|
29
|
+
str(system.get("ManagedSystemID", "")),
|
|
30
|
+
system.get("SystemName", ""),
|
|
31
|
+
system.get("PlatformName", str(system.get("PlatformID", ""))),
|
|
32
|
+
system.get("WorkgroupName", str(system.get("WorkgroupID", ""))),
|
|
33
|
+
str(system.get("Port", "")),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
console.print(table)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def print_system_detail(system: dict) -> None:
|
|
40
|
+
"""Print detailed system information."""
|
|
41
|
+
console.print(f"\n[bold cyan]Managed System: {system.get('SystemName', 'Unknown')}[/bold cyan]\n")
|
|
42
|
+
|
|
43
|
+
info_table = Table(show_header=False, box=None)
|
|
44
|
+
info_table.add_column("Field", style="dim")
|
|
45
|
+
info_table.add_column("Value")
|
|
46
|
+
|
|
47
|
+
fields = [
|
|
48
|
+
("ID", "ManagedSystemID"),
|
|
49
|
+
("Name", "SystemName"),
|
|
50
|
+
("Platform", "PlatformName"),
|
|
51
|
+
("Platform ID", "PlatformID"),
|
|
52
|
+
("Workgroup", "WorkgroupName"),
|
|
53
|
+
("Workgroup ID", "WorkgroupID"),
|
|
54
|
+
("Asset ID", "AssetID"),
|
|
55
|
+
("Port", "Port"),
|
|
56
|
+
("Timeout", "Timeout"),
|
|
57
|
+
("Contact Email", "ContactEmail"),
|
|
58
|
+
("Description", "Description"),
|
|
59
|
+
("Functional Account", "FunctionalAccountName"),
|
|
60
|
+
("Functional Account ID", "FunctionalAccountID"),
|
|
61
|
+
("Elevation Command", "ElevationCommand"),
|
|
62
|
+
("Is Reachable", "IsReachable"),
|
|
63
|
+
("Check Password", "CheckPasswordFlag"),
|
|
64
|
+
("Change After Release", "ChangePasswordAfterAnyReleaseFlag"),
|
|
65
|
+
("Reset On Mismatch", "ResetPasswordOnMismatchFlag"),
|
|
66
|
+
("Change Frequency Type", "ChangeFrequencyType"),
|
|
67
|
+
("Change Frequency Days", "ChangeFrequencyDays"),
|
|
68
|
+
("Change Time", "ChangeTime"),
|
|
69
|
+
("Last Change Date", "LastChangeDate"),
|
|
70
|
+
("Created Date", "CreatedDate"),
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
for label, key in fields:
|
|
74
|
+
value = system.get(key)
|
|
75
|
+
if value is not None:
|
|
76
|
+
if isinstance(value, bool):
|
|
77
|
+
value = "Yes" if value else "No"
|
|
78
|
+
info_table.add_row(label, str(value))
|
|
79
|
+
|
|
80
|
+
console.print(info_table)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@app.command("list")
|
|
84
|
+
def list_systems(
|
|
85
|
+
workgroup: Optional[int] = typer.Option(None, "--workgroup", "-w", help="Filter by workgroup ID"),
|
|
86
|
+
search: Optional[str] = typer.Option(None, "--search", "-s", help="Search filter"),
|
|
87
|
+
limit: int = typer.Option(50, "--limit", "-l", help="Maximum results (default: 50)"),
|
|
88
|
+
fetch_all: bool = typer.Option(False, "--all", help="Fetch all results (may be slow)"),
|
|
89
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
90
|
+
) -> None:
|
|
91
|
+
"""List managed systems.
|
|
92
|
+
|
|
93
|
+
Examples:
|
|
94
|
+
bt pws systems list # First 50 systems
|
|
95
|
+
bt pws systems list --all # All systems
|
|
96
|
+
bt pws systems list -w 3 # Systems in workgroup 3
|
|
97
|
+
bt pws systems list -s axion # Search for 'axion'
|
|
98
|
+
"""
|
|
99
|
+
try:
|
|
100
|
+
with get_client() as client:
|
|
101
|
+
client.authenticate()
|
|
102
|
+
systems = client.list_managed_systems(
|
|
103
|
+
workgroup_id=workgroup,
|
|
104
|
+
search=search,
|
|
105
|
+
limit=None if fetch_all else limit,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if output == "json":
|
|
109
|
+
console.print_json(json.dumps(systems, default=str))
|
|
110
|
+
else:
|
|
111
|
+
if systems:
|
|
112
|
+
print_systems_table(systems)
|
|
113
|
+
if not fetch_all and len(systems) == limit:
|
|
114
|
+
console.print(f"[dim]Showing {len(systems)} results. Use --all to fetch all results.[/dim]")
|
|
115
|
+
else:
|
|
116
|
+
console.print("[yellow]No managed systems found.[/yellow]")
|
|
117
|
+
|
|
118
|
+
except httpx.HTTPStatusError as e:
|
|
119
|
+
print_api_error(e, "manage systems")
|
|
120
|
+
raise typer.Exit(1)
|
|
121
|
+
except httpx.RequestError as e:
|
|
122
|
+
print_api_error(e, "manage systems")
|
|
123
|
+
raise typer.Exit(1)
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print_api_error(e, "manage systems")
|
|
126
|
+
raise typer.Exit(1)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@app.command("get")
|
|
130
|
+
def get_system(
|
|
131
|
+
system_id: int = typer.Argument(..., help="Managed system ID"),
|
|
132
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
133
|
+
) -> None:
|
|
134
|
+
"""Get a managed system by ID."""
|
|
135
|
+
try:
|
|
136
|
+
with get_client() as client:
|
|
137
|
+
client.authenticate()
|
|
138
|
+
system = client.get_managed_system(system_id)
|
|
139
|
+
|
|
140
|
+
if output == "json":
|
|
141
|
+
console.print_json(json.dumps(system, default=str))
|
|
142
|
+
else:
|
|
143
|
+
print_system_detail(system)
|
|
144
|
+
|
|
145
|
+
except httpx.HTTPStatusError as e:
|
|
146
|
+
print_api_error(e, "manage systems")
|
|
147
|
+
raise typer.Exit(1)
|
|
148
|
+
except httpx.RequestError as e:
|
|
149
|
+
print_api_error(e, "manage systems")
|
|
150
|
+
raise typer.Exit(1)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
print_api_error(e, "manage systems")
|
|
153
|
+
raise typer.Exit(1)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@app.command("create")
|
|
157
|
+
def create_system(
|
|
158
|
+
name: str = typer.Option(..., "--name", "-n", help="System name"),
|
|
159
|
+
platform_id: int = typer.Option(..., "--platform-id", "-p", help="Platform ID (1=Windows, 2=Linux, 25=AD, 47=AWS, 79=PostgreSQL, 84=Entra ID). Run 'bt pws platforms list' for all."),
|
|
160
|
+
workgroup_id: Optional[int] = typer.Option(None, "--workgroup-id", "-w", help="Workgroup ID"),
|
|
161
|
+
asset_id: Optional[int] = typer.Option(None, "--asset-id", "-a", help="Asset ID"),
|
|
162
|
+
description: Optional[str] = typer.Option(None, "--description", "-d", help="Description"),
|
|
163
|
+
port: Optional[int] = typer.Option(None, "--port", help="Connection port"),
|
|
164
|
+
timeout: Optional[int] = typer.Option(None, "--timeout", help="Connection timeout"),
|
|
165
|
+
contact_email: Optional[str] = typer.Option(None, "--contact-email", help="Contact email"),
|
|
166
|
+
functional_account_id: Optional[int] = typer.Option(None, "--functional-account-id", help="Functional account ID"),
|
|
167
|
+
elevation_command: Optional[str] = typer.Option(None, "--elevation-command", help="Privilege elevation command (e.g., sudo)"),
|
|
168
|
+
auto_management: bool = typer.Option(False, "--auto-management/--no-auto-management", help="Enable automatic password management"),
|
|
169
|
+
check_password: bool = typer.Option(False, "--check-password/--no-check-password", help="Enable password checking"),
|
|
170
|
+
change_after_release: bool = typer.Option(False, "--change-after-release/--no-change-after-release", help="Change password after release"),
|
|
171
|
+
reset_on_mismatch: bool = typer.Option(False, "--reset-on-mismatch/--no-reset-on-mismatch", help="Reset password on mismatch"),
|
|
172
|
+
change_frequency_type: Optional[str] = typer.Option(None, "--change-frequency-type", help="Change frequency: first, last, xdays"),
|
|
173
|
+
change_frequency_days: Optional[int] = typer.Option(None, "--change-frequency-days", help="Days between changes (if xdays)"),
|
|
174
|
+
change_time: Optional[str] = typer.Option(None, "--change-time", help="Time for changes (HH:MM)"),
|
|
175
|
+
password_rule_id: Optional[int] = typer.Option(None, "--password-rule-id", help="Password rule ID"),
|
|
176
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
177
|
+
) -> None:
|
|
178
|
+
"""Create a new managed system.
|
|
179
|
+
|
|
180
|
+
Common Platform IDs: 1=Windows, 2=Linux, 25=Active Directory,
|
|
181
|
+
47=AWS, 79=PostgreSQL, 84=Entra ID. Use 'bt pws platforms list' for all.
|
|
182
|
+
"""
|
|
183
|
+
try:
|
|
184
|
+
with get_client() as client:
|
|
185
|
+
client.authenticate()
|
|
186
|
+
system = client.create_managed_system(
|
|
187
|
+
system_name=name,
|
|
188
|
+
platform_id=platform_id,
|
|
189
|
+
workgroup_id=workgroup_id,
|
|
190
|
+
asset_id=asset_id,
|
|
191
|
+
description=description,
|
|
192
|
+
port=port,
|
|
193
|
+
timeout=timeout,
|
|
194
|
+
contact_email=contact_email,
|
|
195
|
+
functional_account_id=functional_account_id,
|
|
196
|
+
elevation_command=elevation_command,
|
|
197
|
+
auto_management_flag=auto_management if auto_management else None,
|
|
198
|
+
check_password_flag=check_password if check_password else None,
|
|
199
|
+
change_password_after_any_release_flag=change_after_release if change_after_release else None,
|
|
200
|
+
reset_password_on_mismatch_flag=reset_on_mismatch if reset_on_mismatch else None,
|
|
201
|
+
change_frequency_type=change_frequency_type,
|
|
202
|
+
change_frequency_days=change_frequency_days,
|
|
203
|
+
change_time=change_time,
|
|
204
|
+
password_rule_id=password_rule_id,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if output == "json":
|
|
208
|
+
console.print_json(json.dumps(system, default=str))
|
|
209
|
+
else:
|
|
210
|
+
console.print(f"[green]Created managed system:[/green] {system.get('SystemName', 'Unknown')}")
|
|
211
|
+
console.print(f" ID: {system.get('ManagedSystemID', 'N/A')}")
|
|
212
|
+
|
|
213
|
+
except httpx.HTTPStatusError as e:
|
|
214
|
+
print_api_error(e, "manage systems")
|
|
215
|
+
raise typer.Exit(1)
|
|
216
|
+
except httpx.RequestError as e:
|
|
217
|
+
print_api_error(e, "manage systems")
|
|
218
|
+
raise typer.Exit(1)
|
|
219
|
+
except Exception as e:
|
|
220
|
+
print_api_error(e, "manage systems")
|
|
221
|
+
raise typer.Exit(1)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@app.command("delete")
|
|
225
|
+
def delete_system(
|
|
226
|
+
system: str = typer.Argument(..., help="System ID or name (partial match supported)"),
|
|
227
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
228
|
+
cascade: bool = typer.Option(False, "--cascade", "-c", help="Delete all accounts first"),
|
|
229
|
+
) -> None:
|
|
230
|
+
"""Delete a managed system.
|
|
231
|
+
|
|
232
|
+
Accepts either a numeric ID or a system name (partial match supported).
|
|
233
|
+
|
|
234
|
+
Note: Password Safe won't delete a system that has accounts.
|
|
235
|
+
Use --cascade to delete accounts first, or use 'bt pws quick offboard'.
|
|
236
|
+
|
|
237
|
+
Examples:
|
|
238
|
+
bt pws systems delete 57
|
|
239
|
+
bt pws systems delete axion-finapp-01
|
|
240
|
+
bt pws systems delete axion --cascade
|
|
241
|
+
"""
|
|
242
|
+
try:
|
|
243
|
+
with get_client() as client:
|
|
244
|
+
client.authenticate()
|
|
245
|
+
|
|
246
|
+
# Try to parse as integer ID first
|
|
247
|
+
system_id: int
|
|
248
|
+
system_name: str
|
|
249
|
+
try:
|
|
250
|
+
system_id = int(system)
|
|
251
|
+
sys_data = client.get_managed_system(system_id)
|
|
252
|
+
system_name = sys_data.get("SystemName", "Unknown")
|
|
253
|
+
except ValueError:
|
|
254
|
+
# Not an integer - search by name
|
|
255
|
+
console.print(f"[dim]Searching for system '{system}'...[/dim]")
|
|
256
|
+
systems = client.list_managed_systems(search=system)
|
|
257
|
+
if not systems:
|
|
258
|
+
console.print(f"[red]Error:[/red] No system found matching '{system}'")
|
|
259
|
+
raise typer.Exit(1)
|
|
260
|
+
|
|
261
|
+
# Try exact match first
|
|
262
|
+
matched = None
|
|
263
|
+
for s in systems:
|
|
264
|
+
if s.get("SystemName", "").lower() == system.lower():
|
|
265
|
+
matched = s
|
|
266
|
+
break
|
|
267
|
+
if not matched:
|
|
268
|
+
matched = systems[0]
|
|
269
|
+
if len(systems) > 1:
|
|
270
|
+
console.print(f"[yellow]Multiple matches found, using: {matched.get('SystemName')}[/yellow]")
|
|
271
|
+
|
|
272
|
+
system_id = matched.get("ManagedSystemID")
|
|
273
|
+
system_name = matched.get("SystemName", "Unknown")
|
|
274
|
+
|
|
275
|
+
# Check for accounts if cascade requested or for helpful error
|
|
276
|
+
accounts = client.list_managed_accounts(system_id=system_id)
|
|
277
|
+
if accounts and not cascade:
|
|
278
|
+
console.print(f"[red]Error:[/red] System '{system_name}' has {len(accounts)} managed account(s).")
|
|
279
|
+
console.print("[yellow]Password Safe requires accounts be deleted first.[/yellow]")
|
|
280
|
+
console.print("\nOptions:")
|
|
281
|
+
console.print(f" 1. Use --cascade flag: bt pws systems delete {system} --cascade")
|
|
282
|
+
console.print(f" 2. Use quick offboard: bt pws quick offboard -s \"{system_name}\"")
|
|
283
|
+
raise typer.Exit(1)
|
|
284
|
+
|
|
285
|
+
# Confirmation
|
|
286
|
+
if not force:
|
|
287
|
+
msg = f"Delete system '{system_name}' (ID: {system_id})?"
|
|
288
|
+
if cascade and accounts:
|
|
289
|
+
msg = f"Delete system '{system_name}' and {len(accounts)} account(s)?"
|
|
290
|
+
if not typer.confirm(msg):
|
|
291
|
+
console.print("[yellow]Cancelled.[/yellow]")
|
|
292
|
+
raise typer.Exit(0)
|
|
293
|
+
|
|
294
|
+
# Delete accounts first if cascade
|
|
295
|
+
if cascade and accounts:
|
|
296
|
+
for acc in accounts:
|
|
297
|
+
acc_id = acc.get("AccountId", acc.get("ManagedAccountID"))
|
|
298
|
+
acc_name = acc.get("AccountName", "Unknown")
|
|
299
|
+
console.print(f"[dim]Deleting account '{acc_name}'...[/dim]")
|
|
300
|
+
client.delete_managed_account(acc_id)
|
|
301
|
+
console.print(f" [green]Deleted account: {acc_name}[/green]")
|
|
302
|
+
|
|
303
|
+
# Delete the system
|
|
304
|
+
client.delete_managed_system(system_id)
|
|
305
|
+
console.print(f"[green]Deleted managed system: {system_name} (ID: {system_id})[/green]")
|
|
306
|
+
|
|
307
|
+
except httpx.HTTPStatusError as e:
|
|
308
|
+
# Add helpful context for 400 errors
|
|
309
|
+
if e.response.status_code == 400:
|
|
310
|
+
console.print(f"[red]Error:[/red] Failed to delete system - it may have associated accounts.")
|
|
311
|
+
console.print(f"[yellow]Try: bt pws systems delete {system} --cascade[/yellow]")
|
|
312
|
+
raise typer.Exit(1)
|
|
313
|
+
print_api_error(e, "delete system")
|
|
314
|
+
raise typer.Exit(1)
|
|
315
|
+
except httpx.RequestError as e:
|
|
316
|
+
print_api_error(e, "delete system")
|
|
317
|
+
raise typer.Exit(1)
|
|
318
|
+
except typer.Exit:
|
|
319
|
+
raise
|
|
320
|
+
except Exception as e:
|
|
321
|
+
print_api_error(e, "delete system")
|
|
322
|
+
raise typer.Exit(1)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@app.command("update")
|
|
326
|
+
def update_system(
|
|
327
|
+
system_id: int = typer.Argument(..., help="Managed system ID"),
|
|
328
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="New system name"),
|
|
329
|
+
description: Optional[str] = typer.Option(None, "--description", "-d", help="New description"),
|
|
330
|
+
dns: Optional[str] = typer.Option(None, "--dns", help="New DNS name (updates linked asset)"),
|
|
331
|
+
port: Optional[int] = typer.Option(None, "--port", help="New connection port"),
|
|
332
|
+
timeout: Optional[int] = typer.Option(None, "--timeout", help="New timeout"),
|
|
333
|
+
contact_email: Optional[str] = typer.Option(None, "--contact-email", help="New contact email"),
|
|
334
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
335
|
+
) -> None:
|
|
336
|
+
"""Update a managed system.
|
|
337
|
+
|
|
338
|
+
Note: --dns updates the linked asset's DNS name, not the system itself.
|
|
339
|
+
"""
|
|
340
|
+
try:
|
|
341
|
+
# Build update kwargs
|
|
342
|
+
kwargs = {}
|
|
343
|
+
if name is not None:
|
|
344
|
+
kwargs["system_name"] = name
|
|
345
|
+
if description is not None:
|
|
346
|
+
kwargs["description"] = description
|
|
347
|
+
if port is not None:
|
|
348
|
+
kwargs["port"] = port
|
|
349
|
+
if timeout is not None:
|
|
350
|
+
kwargs["timeout"] = timeout
|
|
351
|
+
if contact_email is not None:
|
|
352
|
+
kwargs["contact_email"] = contact_email
|
|
353
|
+
|
|
354
|
+
if not kwargs and not dns:
|
|
355
|
+
console.print("[yellow]No updates specified.[/yellow]")
|
|
356
|
+
raise typer.Exit(0)
|
|
357
|
+
|
|
358
|
+
with get_client() as client:
|
|
359
|
+
client.authenticate()
|
|
360
|
+
|
|
361
|
+
# Update system properties
|
|
362
|
+
if kwargs:
|
|
363
|
+
system = client.update_managed_system(system_id, **kwargs)
|
|
364
|
+
else:
|
|
365
|
+
system = client.get_managed_system(system_id)
|
|
366
|
+
|
|
367
|
+
# Update DNS on linked asset if requested
|
|
368
|
+
if dns:
|
|
369
|
+
asset_id = system.get("AssetID")
|
|
370
|
+
if asset_id:
|
|
371
|
+
client.update_asset(asset_id, dns_name=dns)
|
|
372
|
+
console.print(f"[dim]Updated DNS on asset {asset_id}[/dim]")
|
|
373
|
+
else:
|
|
374
|
+
console.print("[yellow]Warning: No linked asset found for DNS update[/yellow]")
|
|
375
|
+
|
|
376
|
+
if output == "json":
|
|
377
|
+
console.print_json(json.dumps(system, default=str))
|
|
378
|
+
else:
|
|
379
|
+
console.print(f"[green]Updated managed system ID: {system_id}[/green]")
|
|
380
|
+
|
|
381
|
+
except httpx.HTTPStatusError as e:
|
|
382
|
+
print_api_error(e, "manage systems")
|
|
383
|
+
raise typer.Exit(1)
|
|
384
|
+
except httpx.RequestError as e:
|
|
385
|
+
print_api_error(e, "manage systems")
|
|
386
|
+
raise typer.Exit(1)
|
|
387
|
+
except Exception as e:
|
|
388
|
+
print_api_error(e, "manage systems")
|
|
389
|
+
raise typer.Exit(1)
|