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,33 @@
|
|
|
1
|
+
"""Password Safe CLI commands."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
app = typer.Typer(
|
|
6
|
+
name="pws",
|
|
7
|
+
help="Password Safe management commands",
|
|
8
|
+
no_args_is_help=True,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
# Import and register command groups
|
|
12
|
+
from . import auth, systems, accounts, assets, workgroups, platforms, users
|
|
13
|
+
from . import credentials, config, databases, directories, clouds, secrets, quick, functional, search
|
|
14
|
+
from .import_export import import_app, export_app
|
|
15
|
+
|
|
16
|
+
app.add_typer(auth.app, name="auth", help="Authentication commands")
|
|
17
|
+
app.add_typer(search.app, name="search", help="Search across all PWS entities")
|
|
18
|
+
app.add_typer(systems.app, name="systems", help="Manage managed systems")
|
|
19
|
+
app.add_typer(accounts.app, name="accounts", help="Manage managed accounts (Note: for FUNCTIONAL accounts see 'bt pws functional')")
|
|
20
|
+
app.add_typer(assets.app, name="assets", help="BeyondInsight assets")
|
|
21
|
+
app.add_typer(workgroups.app, name="workgroups", help="Manage workgroups")
|
|
22
|
+
app.add_typer(platforms.app, name="platforms", help="View available platforms")
|
|
23
|
+
app.add_typer(credentials.app, name="credentials", help="Credential checkout/checkin")
|
|
24
|
+
app.add_typer(config.app, name="config", help="View Password Safe configuration")
|
|
25
|
+
app.add_typer(databases.app, name="databases", help="Manage database instances")
|
|
26
|
+
app.add_typer(directories.app, name="directories", help="Manage directories (Entra ID, AD)")
|
|
27
|
+
app.add_typer(clouds.app, name="clouds", help="Manage cloud systems (AWS)")
|
|
28
|
+
app.add_typer(secrets.app, name="secrets", help="Secrets Safe (folders and secrets)")
|
|
29
|
+
app.add_typer(functional.app, name="functional", help="Functional accounts for auto-management")
|
|
30
|
+
app.add_typer(users.app, name="users", help="Manage users, user groups, and roles")
|
|
31
|
+
app.add_typer(quick.app, name="quick", help="Quick commands - common multi-step operations")
|
|
32
|
+
app.add_typer(import_app, name="import", help="Import resources from CSV")
|
|
33
|
+
app.add_typer(export_app, name="export", help="Export sample CSV templates")
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""CLI commands for managing managed accounts."""
|
|
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 accounts in Password Safe.
|
|
15
|
+
|
|
16
|
+
Note: For FUNCTIONAL accounts (used for auto-management/elevation),
|
|
17
|
+
see: bt pws functional
|
|
18
|
+
|
|
19
|
+
See also:
|
|
20
|
+
bt pws systems - Manage systems that contain accounts
|
|
21
|
+
bt pws credentials - Check out/in account passwords
|
|
22
|
+
bt pws functional - Manage functional accounts""")
|
|
23
|
+
console = Console()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def print_accounts_table(accounts: list[dict], title: str = "Managed Accounts") -> None:
|
|
27
|
+
"""Print accounts in a formatted table."""
|
|
28
|
+
table = Table(title=title)
|
|
29
|
+
table.add_column("Acct ID", style="cyan", no_wrap=True)
|
|
30
|
+
table.add_column("Account Name", style="green")
|
|
31
|
+
table.add_column("Domain", style="yellow")
|
|
32
|
+
table.add_column("Sys ID", style="blue", no_wrap=True)
|
|
33
|
+
table.add_column("System Name", style="magenta")
|
|
34
|
+
|
|
35
|
+
for account in accounts:
|
|
36
|
+
# List endpoint uses AccountId/SystemId, Get uses ManagedAccountID/ManagedSystemID
|
|
37
|
+
account_id = account.get("AccountId", account.get("ManagedAccountID", ""))
|
|
38
|
+
system_id = account.get("SystemId", account.get("ManagedSystemID", ""))
|
|
39
|
+
table.add_row(
|
|
40
|
+
str(account_id),
|
|
41
|
+
account.get("AccountName", ""),
|
|
42
|
+
account.get("DomainName") or "-",
|
|
43
|
+
str(system_id),
|
|
44
|
+
account.get("SystemName", "-"),
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
console.print(table)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def print_account_detail(account: dict) -> None:
|
|
51
|
+
"""Print detailed account information."""
|
|
52
|
+
console.print(f"\n[bold cyan]Managed Account: {account.get('AccountName', 'Unknown')}[/bold cyan]\n")
|
|
53
|
+
|
|
54
|
+
info_table = Table(show_header=False, box=None)
|
|
55
|
+
info_table.add_column("Field", style="dim")
|
|
56
|
+
info_table.add_column("Value")
|
|
57
|
+
|
|
58
|
+
fields = [
|
|
59
|
+
("ID", "ManagedAccountID"),
|
|
60
|
+
("Account Name", "AccountName"),
|
|
61
|
+
("Domain", "DomainName"),
|
|
62
|
+
("System ID", "ManagedSystemID"),
|
|
63
|
+
("System Name", "SystemName"),
|
|
64
|
+
("Description", "Description"),
|
|
65
|
+
("API Enabled", "ApiEnabled"),
|
|
66
|
+
("Auto Management", "AutoManagementFlag"),
|
|
67
|
+
("Check Password", "CheckPasswordFlag"),
|
|
68
|
+
("Change After Release", "ChangePasswordAfterAnyReleaseFlag"),
|
|
69
|
+
("Reset On Mismatch", "ResetPasswordOnMismatchFlag"),
|
|
70
|
+
("Change Frequency Type", "ChangeFrequencyType"),
|
|
71
|
+
("Change Frequency Days", "ChangeFrequencyDays"),
|
|
72
|
+
("Change Time", "ChangeTime"),
|
|
73
|
+
("Next Change Date", "NextChangeDate"),
|
|
74
|
+
("Last Change Date", "LastChangeDate"),
|
|
75
|
+
("Password Rule ID", "PasswordRuleID"),
|
|
76
|
+
("DSS Key Rule ID", "DSSKeyRuleID"),
|
|
77
|
+
("Status", "Status"),
|
|
78
|
+
("Created Date", "CreatedDate"),
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
for label, key in fields:
|
|
82
|
+
value = account.get(key)
|
|
83
|
+
if value is not None:
|
|
84
|
+
if isinstance(value, bool):
|
|
85
|
+
value = "Yes" if value else "No"
|
|
86
|
+
info_table.add_row(label, str(value))
|
|
87
|
+
|
|
88
|
+
console.print(info_table)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@app.command("list")
|
|
92
|
+
def list_accounts(
|
|
93
|
+
system: Optional[int] = typer.Option(None, "--system", "-s", help="Filter by system ID"),
|
|
94
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="Filter by account name (exact match)"),
|
|
95
|
+
limit: int = typer.Option(50, "--limit", "-l", help="Maximum results (default: 50)"),
|
|
96
|
+
offset: Optional[int] = typer.Option(None, "--offset", help="Skip first N results (pagination)"),
|
|
97
|
+
fetch_all: bool = typer.Option(False, "--all", help="Fetch all results (may be slow)"),
|
|
98
|
+
api_enabled: Optional[bool] = typer.Option(None, "--api-enabled", help="Filter by API enabled"),
|
|
99
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
100
|
+
) -> None:
|
|
101
|
+
"""List managed accounts.
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
bt pws accounts list # First 50 accounts
|
|
105
|
+
bt pws accounts list --all # All accounts
|
|
106
|
+
bt pws accounts list -s 22 # Accounts on system 22
|
|
107
|
+
bt pws accounts list -n root # Filter by name 'root'
|
|
108
|
+
bt pws accounts list -l 20 # First 20 accounts
|
|
109
|
+
bt pws accounts list -l 20 --offset 20 # Next 20 accounts
|
|
110
|
+
"""
|
|
111
|
+
try:
|
|
112
|
+
with get_client() as client:
|
|
113
|
+
client.authenticate()
|
|
114
|
+
accounts = client.list_managed_accounts(
|
|
115
|
+
system_id=system,
|
|
116
|
+
account_name=name,
|
|
117
|
+
limit=None if fetch_all else limit,
|
|
118
|
+
offset=offset,
|
|
119
|
+
api_enabled=api_enabled,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if output == "json":
|
|
123
|
+
console.print_json(json.dumps(accounts, default=str))
|
|
124
|
+
else:
|
|
125
|
+
if accounts:
|
|
126
|
+
print_accounts_table(accounts)
|
|
127
|
+
# Show pagination hint if results may be truncated
|
|
128
|
+
if not fetch_all and len(accounts) == limit:
|
|
129
|
+
next_offset = (offset or 0) + limit
|
|
130
|
+
console.print(f"[dim]Showing {len(accounts)} results. Use --offset {next_offset} for next page, or --all for all results.[/dim]")
|
|
131
|
+
else:
|
|
132
|
+
console.print("[yellow]No managed accounts found.[/yellow]")
|
|
133
|
+
|
|
134
|
+
except httpx.HTTPStatusError as e:
|
|
135
|
+
print_api_error(e, "manage accounts")
|
|
136
|
+
raise typer.Exit(1)
|
|
137
|
+
except httpx.RequestError as e:
|
|
138
|
+
print_api_error(e, "manage accounts")
|
|
139
|
+
raise typer.Exit(1)
|
|
140
|
+
except Exception as e:
|
|
141
|
+
print_api_error(e, "manage accounts")
|
|
142
|
+
raise typer.Exit(1)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@app.command("get")
|
|
146
|
+
def get_account(
|
|
147
|
+
account_id: int = typer.Argument(..., help="Managed account ID"),
|
|
148
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
149
|
+
) -> None:
|
|
150
|
+
"""Get a managed account by ID."""
|
|
151
|
+
try:
|
|
152
|
+
with get_client() as client:
|
|
153
|
+
client.authenticate()
|
|
154
|
+
account = client.get_managed_account(account_id)
|
|
155
|
+
|
|
156
|
+
if output == "json":
|
|
157
|
+
console.print_json(json.dumps(account, default=str))
|
|
158
|
+
else:
|
|
159
|
+
print_account_detail(account)
|
|
160
|
+
|
|
161
|
+
except httpx.HTTPStatusError as e:
|
|
162
|
+
print_api_error(e, "manage accounts")
|
|
163
|
+
raise typer.Exit(1)
|
|
164
|
+
except httpx.RequestError as e:
|
|
165
|
+
print_api_error(e, "manage accounts")
|
|
166
|
+
raise typer.Exit(1)
|
|
167
|
+
except Exception as e:
|
|
168
|
+
print_api_error(e, "manage accounts")
|
|
169
|
+
raise typer.Exit(1)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@app.command("create")
|
|
173
|
+
def create_account(
|
|
174
|
+
system_id: int = typer.Option(..., "--system", "-s", help="System ID"),
|
|
175
|
+
name: str = typer.Option(..., "--name", "-n", help="Account name"),
|
|
176
|
+
password: Optional[str] = typer.Option(None, "--password", "-p", help="Initial password"),
|
|
177
|
+
domain: Optional[str] = typer.Option(None, "--domain", "-d", help="Domain name"),
|
|
178
|
+
description: Optional[str] = typer.Option(None, "--description", help="Description"),
|
|
179
|
+
api_enabled: bool = typer.Option(True, "--api-enabled/--no-api-enabled", help="Enable API access"),
|
|
180
|
+
auto_manage: bool = typer.Option(True, "--auto-manage/--no-auto-manage", help="Enable auto management"),
|
|
181
|
+
change_frequency: Optional[str] = typer.Option(None, "--change-frequency", help="Change frequency: first, last, or xdays"),
|
|
182
|
+
change_days: Optional[int] = typer.Option(None, "--change-days", help="Days between changes (1-90, requires --change-frequency xdays)"),
|
|
183
|
+
change_time: Optional[str] = typer.Option(None, "--change-time", help="Time for changes (HH:MM, e.g., 02:00)"),
|
|
184
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
185
|
+
) -> None:
|
|
186
|
+
"""Create a new managed account.
|
|
187
|
+
|
|
188
|
+
Examples:
|
|
189
|
+
# Basic account
|
|
190
|
+
pws accounts create -s 30 -n "myaccount" -p "Password123"
|
|
191
|
+
|
|
192
|
+
# With daily rotation at 2am
|
|
193
|
+
pws accounts create -s 30 -n "svc_app" -p "Pass123" --change-frequency xdays --change-days 1 --change-time 02:00
|
|
194
|
+
"""
|
|
195
|
+
try:
|
|
196
|
+
with get_client() as client:
|
|
197
|
+
client.authenticate()
|
|
198
|
+
account = client.create_managed_account(
|
|
199
|
+
system_id=system_id,
|
|
200
|
+
account_name=name,
|
|
201
|
+
password=password,
|
|
202
|
+
domain_name=domain,
|
|
203
|
+
description=description,
|
|
204
|
+
api_enabled=api_enabled,
|
|
205
|
+
auto_management_flag=auto_manage,
|
|
206
|
+
change_frequency_type=change_frequency,
|
|
207
|
+
change_frequency_days=change_days,
|
|
208
|
+
change_time=change_time,
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
if output == "json":
|
|
212
|
+
console.print_json(json.dumps(account, default=str))
|
|
213
|
+
else:
|
|
214
|
+
console.print(f"[green]Created managed account:[/green] {account.get('AccountName', 'Unknown')}")
|
|
215
|
+
console.print(f" ID: {account.get('ManagedAccountID', 'N/A')}")
|
|
216
|
+
if change_frequency:
|
|
217
|
+
console.print(f" Rotation: {change_frequency}" + (f" ({change_days} days)" if change_days else ""))
|
|
218
|
+
|
|
219
|
+
except httpx.HTTPStatusError as e:
|
|
220
|
+
print_api_error(e, "manage accounts")
|
|
221
|
+
raise typer.Exit(1)
|
|
222
|
+
except httpx.RequestError as e:
|
|
223
|
+
print_api_error(e, "manage accounts")
|
|
224
|
+
raise typer.Exit(1)
|
|
225
|
+
except Exception as e:
|
|
226
|
+
print_api_error(e, "manage accounts")
|
|
227
|
+
raise typer.Exit(1)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@app.command("delete")
|
|
231
|
+
def delete_account(
|
|
232
|
+
account_id: int = typer.Argument(..., help="Managed account ID to delete"),
|
|
233
|
+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
|
|
234
|
+
) -> None:
|
|
235
|
+
"""Delete a managed account."""
|
|
236
|
+
try:
|
|
237
|
+
with get_client() as client:
|
|
238
|
+
client.authenticate()
|
|
239
|
+
|
|
240
|
+
if not force:
|
|
241
|
+
account = client.get_managed_account(account_id)
|
|
242
|
+
account_name = account.get("AccountName", "Unknown")
|
|
243
|
+
confirm = typer.confirm(
|
|
244
|
+
f"Are you sure you want to delete account '{account_name}' (ID: {account_id})?"
|
|
245
|
+
)
|
|
246
|
+
if not confirm:
|
|
247
|
+
console.print("[yellow]Cancelled.[/yellow]")
|
|
248
|
+
raise typer.Exit(0)
|
|
249
|
+
|
|
250
|
+
client.delete_managed_account(account_id)
|
|
251
|
+
console.print(f"[green]Deleted managed account ID: {account_id}[/green]")
|
|
252
|
+
|
|
253
|
+
except httpx.HTTPStatusError as e:
|
|
254
|
+
print_api_error(e, "manage accounts")
|
|
255
|
+
raise typer.Exit(1)
|
|
256
|
+
except httpx.RequestError as e:
|
|
257
|
+
print_api_error(e, "manage accounts")
|
|
258
|
+
raise typer.Exit(1)
|
|
259
|
+
except Exception as e:
|
|
260
|
+
print_api_error(e, "manage accounts")
|
|
261
|
+
raise typer.Exit(1)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@app.command("update")
|
|
265
|
+
def update_account(
|
|
266
|
+
account_id: int = typer.Argument(..., help="Managed account ID"),
|
|
267
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="New account name"),
|
|
268
|
+
domain: Optional[str] = typer.Option(None, "--domain", "-d", help="New domain"),
|
|
269
|
+
description: Optional[str] = typer.Option(None, "--description", help="New description"),
|
|
270
|
+
api_enabled: Optional[bool] = typer.Option(None, "--api-enabled/--no-api-enabled", help="API access"),
|
|
271
|
+
auto_manage: Optional[bool] = typer.Option(None, "--auto-manage/--no-auto-manage", help="Auto management"),
|
|
272
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table or json"),
|
|
273
|
+
) -> None:
|
|
274
|
+
"""Update a managed account."""
|
|
275
|
+
try:
|
|
276
|
+
kwargs = {}
|
|
277
|
+
if name is not None:
|
|
278
|
+
kwargs["account_name"] = name
|
|
279
|
+
if domain is not None:
|
|
280
|
+
kwargs["domain_name"] = domain
|
|
281
|
+
if description is not None:
|
|
282
|
+
kwargs["description"] = description
|
|
283
|
+
if api_enabled is not None:
|
|
284
|
+
kwargs["api_enabled"] = api_enabled
|
|
285
|
+
if auto_manage is not None:
|
|
286
|
+
kwargs["auto_management_flag"] = auto_manage
|
|
287
|
+
|
|
288
|
+
if not kwargs:
|
|
289
|
+
console.print("[yellow]No updates specified.[/yellow]")
|
|
290
|
+
raise typer.Exit(0)
|
|
291
|
+
|
|
292
|
+
with get_client() as client:
|
|
293
|
+
client.authenticate()
|
|
294
|
+
account = client.update_managed_account(account_id, **kwargs)
|
|
295
|
+
|
|
296
|
+
if output == "json":
|
|
297
|
+
console.print_json(json.dumps(account, default=str))
|
|
298
|
+
else:
|
|
299
|
+
console.print(f"[green]Updated managed account ID: {account_id}[/green]")
|
|
300
|
+
|
|
301
|
+
except httpx.HTTPStatusError as e:
|
|
302
|
+
print_api_error(e, "manage accounts")
|
|
303
|
+
raise typer.Exit(1)
|
|
304
|
+
except httpx.RequestError as e:
|
|
305
|
+
print_api_error(e, "manage accounts")
|
|
306
|
+
raise typer.Exit(1)
|
|
307
|
+
except Exception as e:
|
|
308
|
+
print_api_error(e, "manage accounts")
|
|
309
|
+
raise typer.Exit(1)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@app.command("set-password")
|
|
313
|
+
def set_password(
|
|
314
|
+
account_id: int = typer.Argument(..., help="Managed account ID"),
|
|
315
|
+
password: str = typer.Option(..., "--password", "-p", help="New password", prompt=True, hide_input=True),
|
|
316
|
+
) -> None:
|
|
317
|
+
"""Set the password for a managed account."""
|
|
318
|
+
try:
|
|
319
|
+
with get_client() as client:
|
|
320
|
+
client.authenticate()
|
|
321
|
+
client.set_managed_account_password(account_id, password)
|
|
322
|
+
console.print(f"[green]Password updated for account ID: {account_id}[/green]")
|
|
323
|
+
|
|
324
|
+
except httpx.HTTPStatusError as e:
|
|
325
|
+
print_api_error(e, "manage accounts")
|
|
326
|
+
raise typer.Exit(1)
|
|
327
|
+
except httpx.RequestError as e:
|
|
328
|
+
print_api_error(e, "manage accounts")
|
|
329
|
+
raise typer.Exit(1)
|
|
330
|
+
except Exception as e:
|
|
331
|
+
print_api_error(e, "manage accounts")
|
|
332
|
+
raise typer.Exit(1)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
@app.command("rotate")
|
|
336
|
+
def rotate_password(
|
|
337
|
+
account_id: int = typer.Argument(..., help="Managed account ID to rotate"),
|
|
338
|
+
) -> None:
|
|
339
|
+
"""Trigger password rotation for a managed account.
|
|
340
|
+
|
|
341
|
+
This initiates a password change using the configured password rule.
|
|
342
|
+
The system generates a new password and updates it on the target
|
|
343
|
+
system using the functional account.
|
|
344
|
+
"""
|
|
345
|
+
try:
|
|
346
|
+
with get_client() as client:
|
|
347
|
+
client.authenticate()
|
|
348
|
+
|
|
349
|
+
# Get account details for display
|
|
350
|
+
account = client.get_managed_account(account_id)
|
|
351
|
+
account_name = account.get("AccountName", "Unknown")
|
|
352
|
+
system_name = account.get("SystemName", str(account.get("ManagedSystemID", "?")))
|
|
353
|
+
|
|
354
|
+
console.print(f"Rotating password for [cyan]{account_name}[/cyan] on [yellow]{system_name}[/yellow]...")
|
|
355
|
+
|
|
356
|
+
result = client.change_managed_account_password(account_id)
|
|
357
|
+
|
|
358
|
+
console.print(f"[green]Password rotation initiated for account ID: {account_id}[/green]")
|
|
359
|
+
|
|
360
|
+
# Show result if available
|
|
361
|
+
if result:
|
|
362
|
+
console.print_json(json.dumps(result, default=str))
|
|
363
|
+
|
|
364
|
+
except httpx.HTTPStatusError as e:
|
|
365
|
+
print_api_error(e, "manage accounts")
|
|
366
|
+
raise typer.Exit(1)
|
|
367
|
+
except httpx.RequestError as e:
|
|
368
|
+
print_api_error(e, "manage accounts")
|
|
369
|
+
raise typer.Exit(1)
|
|
370
|
+
except Exception as e:
|
|
371
|
+
print_api_error(e, "manage accounts")
|
|
372
|
+
raise typer.Exit(1)
|