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,218 @@
|
|
|
1
|
+
"""Bundle commands for Entitle."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ..client.base import get_client
|
|
9
|
+
from ...core.output import console, print_table, print_json, print_error, print_success, print_api_error
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True, help="Manage bundles")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command("list")
|
|
15
|
+
def list_bundles(
|
|
16
|
+
search: Optional[str] = typer.Option(None, "--search", "-s", help="Search filter"),
|
|
17
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
|
|
18
|
+
) -> None:
|
|
19
|
+
"""List all bundles with full details."""
|
|
20
|
+
try:
|
|
21
|
+
with get_client() as client:
|
|
22
|
+
# List API only returns id/name, so fetch full details for each
|
|
23
|
+
bundles_list = client.list_bundles(search=search)
|
|
24
|
+
|
|
25
|
+
# Fetch full details for each bundle to get description, category, tags
|
|
26
|
+
data = []
|
|
27
|
+
for bundle in bundles_list:
|
|
28
|
+
bundle_id = bundle.get("id")
|
|
29
|
+
if bundle_id:
|
|
30
|
+
response = client.get_bundle(bundle_id)
|
|
31
|
+
# API returns {"result": {...}}, extract the result
|
|
32
|
+
full_bundle = response.get("result", response)
|
|
33
|
+
data.append(full_bundle)
|
|
34
|
+
|
|
35
|
+
if output == "json":
|
|
36
|
+
print_json(data)
|
|
37
|
+
else:
|
|
38
|
+
# Format for display - truncate description, format tags
|
|
39
|
+
display_data = []
|
|
40
|
+
for bundle in data:
|
|
41
|
+
desc = bundle.get("description", "") or ""
|
|
42
|
+
tags = bundle.get("tags", []) or []
|
|
43
|
+
display_data.append({
|
|
44
|
+
"id": bundle.get("id"),
|
|
45
|
+
"name": bundle.get("name"),
|
|
46
|
+
"category": bundle.get("category"),
|
|
47
|
+
"description": desc[:50] + "..." if len(desc) > 50 else desc,
|
|
48
|
+
"tags": ", ".join(tags[:3]) + (f" (+{len(tags)-3})" if len(tags) > 3 else "") if tags else "-",
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
print_table(
|
|
52
|
+
display_data,
|
|
53
|
+
[
|
|
54
|
+
("ID", "id"),
|
|
55
|
+
("Name", "name"),
|
|
56
|
+
("Category", "category"),
|
|
57
|
+
("Description", "description"),
|
|
58
|
+
("Tags", "tags"),
|
|
59
|
+
],
|
|
60
|
+
title="Bundles",
|
|
61
|
+
)
|
|
62
|
+
except httpx.HTTPStatusError as e:
|
|
63
|
+
print_api_error(e, "list bundles")
|
|
64
|
+
raise typer.Exit(1)
|
|
65
|
+
except httpx.RequestError as e:
|
|
66
|
+
print_api_error(e, "list bundles")
|
|
67
|
+
raise typer.Exit(1)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print_api_error(e, "list bundles")
|
|
70
|
+
raise typer.Exit(1)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@app.command("get")
|
|
74
|
+
def get_bundle(
|
|
75
|
+
bundle_id: str = typer.Argument(..., help="Bundle ID"),
|
|
76
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Get a bundle by ID with full details."""
|
|
79
|
+
try:
|
|
80
|
+
with get_client() as client:
|
|
81
|
+
response = client.get_bundle(bundle_id)
|
|
82
|
+
|
|
83
|
+
bundle = response.get("result", response)
|
|
84
|
+
|
|
85
|
+
if output == "json":
|
|
86
|
+
print_json(response)
|
|
87
|
+
else:
|
|
88
|
+
from rich.panel import Panel
|
|
89
|
+
from rich.table import Table
|
|
90
|
+
|
|
91
|
+
# Header info
|
|
92
|
+
name = bundle.get("name", "")
|
|
93
|
+
desc = bundle.get("description", "") or "-"
|
|
94
|
+
category = bundle.get("category", "") or "-"
|
|
95
|
+
tags = bundle.get("tags", []) or []
|
|
96
|
+
durations = bundle.get("allowedDurations", [])
|
|
97
|
+
workflow = bundle.get("workflow", {})
|
|
98
|
+
|
|
99
|
+
# Format durations as human-readable
|
|
100
|
+
def fmt_duration(secs: int) -> str:
|
|
101
|
+
if secs >= 86400:
|
|
102
|
+
return f"{secs // 86400}d"
|
|
103
|
+
elif secs >= 3600:
|
|
104
|
+
return f"{secs // 3600}h"
|
|
105
|
+
else:
|
|
106
|
+
return f"{secs // 60}m"
|
|
107
|
+
|
|
108
|
+
duration_str = ", ".join(fmt_duration(d) for d in durations) if durations else "-"
|
|
109
|
+
|
|
110
|
+
console.print(Panel(
|
|
111
|
+
f"[bold]{name}[/bold]\n\n"
|
|
112
|
+
f"[dim]Category:[/dim] {category}\n"
|
|
113
|
+
f"[dim]Tags:[/dim] {', '.join(tags) if tags else '-'}\n"
|
|
114
|
+
f"[dim]Durations:[/dim] {duration_str}\n"
|
|
115
|
+
f"[dim]Workflow:[/dim] {workflow.get('name', '-')}",
|
|
116
|
+
title="Bundle Details",
|
|
117
|
+
subtitle=f"ID: {bundle.get('id', '')}",
|
|
118
|
+
))
|
|
119
|
+
|
|
120
|
+
console.print(f"\n[dim]{desc}[/dim]\n")
|
|
121
|
+
|
|
122
|
+
# Roles table
|
|
123
|
+
roles = bundle.get("roles", [])
|
|
124
|
+
if roles:
|
|
125
|
+
table = Table(title="Included Roles")
|
|
126
|
+
table.add_column("Role", style="cyan")
|
|
127
|
+
table.add_column("Resource", style="green")
|
|
128
|
+
table.add_column("Integration", style="yellow")
|
|
129
|
+
|
|
130
|
+
for role in roles:
|
|
131
|
+
resource = role.get("resource", {})
|
|
132
|
+
integration = resource.get("integration", {})
|
|
133
|
+
app_name = integration.get("application", {}).get("name", "")
|
|
134
|
+
int_name = integration.get("name", "")
|
|
135
|
+
int_display = f"{int_name} ({app_name})" if app_name else int_name
|
|
136
|
+
|
|
137
|
+
table.add_row(
|
|
138
|
+
role.get("name", "-"),
|
|
139
|
+
resource.get("name", "-"),
|
|
140
|
+
int_display or "-",
|
|
141
|
+
)
|
|
142
|
+
console.print(table)
|
|
143
|
+
else:
|
|
144
|
+
console.print("[yellow]No roles defined[/yellow]")
|
|
145
|
+
|
|
146
|
+
except httpx.HTTPStatusError as e:
|
|
147
|
+
print_api_error(e, "get bundle")
|
|
148
|
+
raise typer.Exit(1)
|
|
149
|
+
except httpx.RequestError as e:
|
|
150
|
+
print_api_error(e, "get bundle")
|
|
151
|
+
raise typer.Exit(1)
|
|
152
|
+
except Exception as e:
|
|
153
|
+
print_api_error(e, "get bundle")
|
|
154
|
+
raise typer.Exit(1)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@app.command("create")
|
|
158
|
+
def create_bundle(
|
|
159
|
+
name: str = typer.Option(..., "--name", "-n", help="Bundle name"),
|
|
160
|
+
description: str = typer.Option(..., "--description", "-d", help="Bundle description"),
|
|
161
|
+
workflow_id: str = typer.Option(..., "--workflow", "-w", help="Workflow ID"),
|
|
162
|
+
role_ids: list[str] = typer.Option([], "--role", "-r", help="Role IDs to include"),
|
|
163
|
+
durations: list[int] = typer.Option([3600], "--duration", help="Allowed durations in seconds"),
|
|
164
|
+
category: Optional[str] = typer.Option(None, "--category", "-c", help="Category"),
|
|
165
|
+
tags: list[str] = typer.Option([], "--tag", "-t", help="Tags"),
|
|
166
|
+
) -> None:
|
|
167
|
+
"""Create a new bundle."""
|
|
168
|
+
try:
|
|
169
|
+
data = {
|
|
170
|
+
"name": name,
|
|
171
|
+
"description": description,
|
|
172
|
+
"workflow": {"id": workflow_id},
|
|
173
|
+
"roles": [{"id": rid} for rid in role_ids],
|
|
174
|
+
"allowedDurations": durations,
|
|
175
|
+
}
|
|
176
|
+
if category:
|
|
177
|
+
data["category"] = category
|
|
178
|
+
if tags:
|
|
179
|
+
data["tags"] = tags
|
|
180
|
+
|
|
181
|
+
with get_client() as client:
|
|
182
|
+
result = client.create_bundle(data)
|
|
183
|
+
|
|
184
|
+
print_success("Bundle created successfully!")
|
|
185
|
+
print_json(result)
|
|
186
|
+
except httpx.HTTPStatusError as e:
|
|
187
|
+
print_api_error(e, "create bundle")
|
|
188
|
+
raise typer.Exit(1)
|
|
189
|
+
except httpx.RequestError as e:
|
|
190
|
+
print_api_error(e, "create bundle")
|
|
191
|
+
raise typer.Exit(1)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
print_api_error(e, "create bundle")
|
|
194
|
+
raise typer.Exit(1)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@app.command("delete")
|
|
198
|
+
def delete_bundle(
|
|
199
|
+
bundle_id: str = typer.Argument(..., help="Bundle ID"),
|
|
200
|
+
confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
201
|
+
) -> None:
|
|
202
|
+
"""Delete a bundle."""
|
|
203
|
+
if not confirm:
|
|
204
|
+
typer.confirm(f"Are you sure you want to delete bundle {bundle_id}?", abort=True)
|
|
205
|
+
|
|
206
|
+
try:
|
|
207
|
+
with get_client() as client:
|
|
208
|
+
client.delete_bundle(bundle_id)
|
|
209
|
+
print_success("Bundle deleted successfully!")
|
|
210
|
+
except httpx.HTTPStatusError as e:
|
|
211
|
+
print_api_error(e, "delete bundle")
|
|
212
|
+
raise typer.Exit(1)
|
|
213
|
+
except httpx.RequestError as e:
|
|
214
|
+
print_api_error(e, "delete bundle")
|
|
215
|
+
raise typer.Exit(1)
|
|
216
|
+
except Exception as e:
|
|
217
|
+
print_api_error(e, "delete bundle")
|
|
218
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Integration commands for Entitle."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ..client.base import get_client
|
|
9
|
+
from ...core.output import console, print_table, print_json, print_error, print_api_error
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True, help="Manage integrations")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command("list")
|
|
15
|
+
def list_integrations(
|
|
16
|
+
search: Optional[str] = typer.Option(None, "--search", "-s", help="Search filter"),
|
|
17
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
|
|
18
|
+
) -> None:
|
|
19
|
+
"""List all integrations."""
|
|
20
|
+
try:
|
|
21
|
+
with get_client() as client:
|
|
22
|
+
data = client.list_integrations(search=search)
|
|
23
|
+
|
|
24
|
+
if output == "json":
|
|
25
|
+
print_json(data)
|
|
26
|
+
else:
|
|
27
|
+
print_table(
|
|
28
|
+
data,
|
|
29
|
+
[("ID", "id"), ("Name", "name"), ("Application", "application"), ("Owner", "owner")],
|
|
30
|
+
title="Integrations",
|
|
31
|
+
)
|
|
32
|
+
except httpx.HTTPStatusError as e:
|
|
33
|
+
print_api_error(e, "list integrations")
|
|
34
|
+
raise typer.Exit(1)
|
|
35
|
+
except httpx.RequestError as e:
|
|
36
|
+
print_api_error(e, "list integrations")
|
|
37
|
+
raise typer.Exit(1)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
print_api_error(e, "list integrations")
|
|
40
|
+
raise typer.Exit(1)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command("get")
|
|
44
|
+
def get_integration(
|
|
45
|
+
integration_id: str = typer.Argument(..., help="Integration ID"),
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Get an integration by ID."""
|
|
48
|
+
try:
|
|
49
|
+
with get_client() as client:
|
|
50
|
+
data = client.get_integration(integration_id)
|
|
51
|
+
print_json(data)
|
|
52
|
+
except httpx.HTTPStatusError as e:
|
|
53
|
+
print_api_error(e, "get integration")
|
|
54
|
+
raise typer.Exit(1)
|
|
55
|
+
except httpx.RequestError as e:
|
|
56
|
+
print_api_error(e, "get integration")
|
|
57
|
+
raise typer.Exit(1)
|
|
58
|
+
except Exception as e:
|
|
59
|
+
print_api_error(e, "get integration")
|
|
60
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Permission commands for Entitle."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ..client.base import get_client
|
|
9
|
+
from ...core.output import console, print_table, print_json, print_error, print_success, print_api_error
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True, help="Manage permissions")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command("list")
|
|
15
|
+
def list_permissions(
|
|
16
|
+
user_id: Optional[str] = typer.Option(None, "--user", "-u", help="User ID filter"),
|
|
17
|
+
integration_id: Optional[str] = typer.Option(None, "--integration", "-i", help="Integration ID filter"),
|
|
18
|
+
resource_id: Optional[str] = typer.Option(None, "--resource", "-r", help="Resource ID filter"),
|
|
19
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
|
|
20
|
+
) -> None:
|
|
21
|
+
"""List permissions."""
|
|
22
|
+
try:
|
|
23
|
+
with get_client() as client:
|
|
24
|
+
data = client.list_permissions(
|
|
25
|
+
user_id=user_id,
|
|
26
|
+
integration_id=integration_id,
|
|
27
|
+
resource_id=resource_id,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
if output == "json":
|
|
31
|
+
print_json(data)
|
|
32
|
+
else:
|
|
33
|
+
print_table(
|
|
34
|
+
data,
|
|
35
|
+
[("ID", "id"), ("User", "user"), ("Role", "role"), ("Resource", "resource"), ("Status", "status"), ("Expires", "expires_at")],
|
|
36
|
+
title="Permissions",
|
|
37
|
+
)
|
|
38
|
+
except httpx.HTTPStatusError as e:
|
|
39
|
+
print_api_error(e, "list permissions")
|
|
40
|
+
raise typer.Exit(1)
|
|
41
|
+
except httpx.RequestError as e:
|
|
42
|
+
print_api_error(e, "list permissions")
|
|
43
|
+
raise typer.Exit(1)
|
|
44
|
+
except Exception as e:
|
|
45
|
+
print_api_error(e, "list permissions")
|
|
46
|
+
raise typer.Exit(1)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@app.command("revoke")
|
|
50
|
+
def revoke_permission(
|
|
51
|
+
permission_id: str = typer.Argument(..., help="Permission ID"),
|
|
52
|
+
confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Revoke a permission."""
|
|
55
|
+
if not confirm:
|
|
56
|
+
typer.confirm(f"Are you sure you want to revoke permission {permission_id}?", abort=True)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
with get_client() as client:
|
|
60
|
+
client.revoke_permission(permission_id)
|
|
61
|
+
print_success("Permission revoked successfully!")
|
|
62
|
+
except httpx.HTTPStatusError as e:
|
|
63
|
+
print_api_error(e, "revoke permission")
|
|
64
|
+
raise typer.Exit(1)
|
|
65
|
+
except httpx.RequestError as e:
|
|
66
|
+
print_api_error(e, "revoke permission")
|
|
67
|
+
raise typer.Exit(1)
|
|
68
|
+
except Exception as e:
|
|
69
|
+
print_api_error(e, "revoke permission")
|
|
70
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Policy commands for Entitle."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ..client.base import get_client
|
|
9
|
+
from ...core.output import console, print_table, print_json, print_error, print_api_error
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True, help="Manage policies")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command("list")
|
|
15
|
+
def list_policies(
|
|
16
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
|
|
17
|
+
) -> None:
|
|
18
|
+
"""List all policies with details."""
|
|
19
|
+
try:
|
|
20
|
+
with get_client() as client:
|
|
21
|
+
# List API only returns id/number/sortOrder, fetch full details
|
|
22
|
+
policies_list = client.list_policies()
|
|
23
|
+
|
|
24
|
+
data = []
|
|
25
|
+
for policy in policies_list:
|
|
26
|
+
policy_id = policy.get("id")
|
|
27
|
+
if policy_id:
|
|
28
|
+
response = client.get_policy(policy_id)
|
|
29
|
+
full_policy = response.get("result", response)
|
|
30
|
+
data.append(full_policy)
|
|
31
|
+
|
|
32
|
+
if output == "json":
|
|
33
|
+
print_json(data)
|
|
34
|
+
else:
|
|
35
|
+
# Format for display - show group names and counts
|
|
36
|
+
display_data = []
|
|
37
|
+
for policy in data:
|
|
38
|
+
in_groups = policy.get("inGroups", [])
|
|
39
|
+
bundles = policy.get("bundles", [])
|
|
40
|
+
roles = policy.get("roles", [])
|
|
41
|
+
|
|
42
|
+
# Show first group name + count
|
|
43
|
+
if in_groups:
|
|
44
|
+
first_group = in_groups[0].get("name", "")[:25]
|
|
45
|
+
groups_display = first_group + (f" (+{len(in_groups)-1})" if len(in_groups) > 1 else "")
|
|
46
|
+
else:
|
|
47
|
+
groups_display = "-"
|
|
48
|
+
|
|
49
|
+
# Show bundle names
|
|
50
|
+
if bundles:
|
|
51
|
+
bundle_names = [b.get("name", "")[:20] for b in bundles[:2]]
|
|
52
|
+
bundles_display = ", ".join(bundle_names) + (f" (+{len(bundles)-2})" if len(bundles) > 2 else "")
|
|
53
|
+
else:
|
|
54
|
+
bundles_display = "-"
|
|
55
|
+
|
|
56
|
+
display_data.append({
|
|
57
|
+
"number": policy.get("number"),
|
|
58
|
+
"inGroups": groups_display,
|
|
59
|
+
"bundles": bundles_display,
|
|
60
|
+
"roles": len(roles) if roles else "-",
|
|
61
|
+
"sortOrder": policy.get("sortOrder"),
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
print_table(
|
|
65
|
+
display_data,
|
|
66
|
+
[("#", "number"), ("Groups", "inGroups"), ("Bundles", "bundles"), ("Roles", "roles"), ("Order", "sortOrder")],
|
|
67
|
+
title="Policies",
|
|
68
|
+
)
|
|
69
|
+
except httpx.HTTPStatusError as e:
|
|
70
|
+
print_api_error(e, "list policies")
|
|
71
|
+
raise typer.Exit(1)
|
|
72
|
+
except httpx.RequestError as e:
|
|
73
|
+
print_api_error(e, "list policies")
|
|
74
|
+
raise typer.Exit(1)
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print_api_error(e, "list policies")
|
|
77
|
+
raise typer.Exit(1)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@app.command("get")
|
|
81
|
+
def get_policy(
|
|
82
|
+
policy_id: str = typer.Argument(..., help="Policy ID"),
|
|
83
|
+
) -> None:
|
|
84
|
+
"""Get a policy by ID."""
|
|
85
|
+
try:
|
|
86
|
+
with get_client() as client:
|
|
87
|
+
data = client.get_policy(policy_id)
|
|
88
|
+
print_json(data)
|
|
89
|
+
except httpx.HTTPStatusError as e:
|
|
90
|
+
print_api_error(e, "get policy")
|
|
91
|
+
raise typer.Exit(1)
|
|
92
|
+
except httpx.RequestError as e:
|
|
93
|
+
print_api_error(e, "get policy")
|
|
94
|
+
raise typer.Exit(1)
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print_api_error(e, "get policy")
|
|
97
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Resource commands for Entitle."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ..client.base import get_client
|
|
9
|
+
from ...core.output import console, print_table, print_json, print_error, print_api_error
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True, help="Manage resources")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command("list")
|
|
15
|
+
def list_resources(
|
|
16
|
+
integration_id: str = typer.Option(..., "--integration", "-i", help="Integration ID (required)"),
|
|
17
|
+
search: Optional[str] = typer.Option(None, "--search", "-s", help="Search filter"),
|
|
18
|
+
limit: int = typer.Option(100, "--limit", "-l", help="Maximum results to return"),
|
|
19
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
|
|
20
|
+
) -> None:
|
|
21
|
+
"""List resources for an integration.
|
|
22
|
+
|
|
23
|
+
The Entitle API requires an integration ID to list resources.
|
|
24
|
+
Use 'bt entitle integrations list' to find integration IDs.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
bt entitle resources list -i <integration_id>
|
|
28
|
+
bt entitle resources list -i <integration_id> -s "search term"
|
|
29
|
+
bt entitle resources list -i <integration_id> -l 50 -o json
|
|
30
|
+
"""
|
|
31
|
+
try:
|
|
32
|
+
with get_client() as client:
|
|
33
|
+
data = client.list_resources(integration_id=integration_id, search=search, limit=limit)
|
|
34
|
+
|
|
35
|
+
if output == "json":
|
|
36
|
+
print_json(data)
|
|
37
|
+
else:
|
|
38
|
+
print_table(
|
|
39
|
+
data,
|
|
40
|
+
[("ID", "id"), ("Name", "name"), ("Integration", "integration"), ("Requestable", "requestable")],
|
|
41
|
+
title="Resources",
|
|
42
|
+
)
|
|
43
|
+
except httpx.HTTPStatusError as e:
|
|
44
|
+
print_api_error(e, "list resources")
|
|
45
|
+
raise typer.Exit(1)
|
|
46
|
+
except httpx.RequestError as e:
|
|
47
|
+
print_api_error(e, "list resources")
|
|
48
|
+
raise typer.Exit(1)
|
|
49
|
+
except Exception as e:
|
|
50
|
+
print_api_error(e, "list resources")
|
|
51
|
+
raise typer.Exit(1)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@app.command("get")
|
|
55
|
+
def get_resource(
|
|
56
|
+
resource_id: str = typer.Argument(..., help="Resource ID"),
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Get a resource by ID."""
|
|
59
|
+
try:
|
|
60
|
+
with get_client() as client:
|
|
61
|
+
data = client.get_resource(resource_id)
|
|
62
|
+
print_json(data)
|
|
63
|
+
except httpx.HTTPStatusError as e:
|
|
64
|
+
print_api_error(e, "get resource")
|
|
65
|
+
raise typer.Exit(1)
|
|
66
|
+
except httpx.RequestError as e:
|
|
67
|
+
print_api_error(e, "get resource")
|
|
68
|
+
raise typer.Exit(1)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print_api_error(e, "get resource")
|
|
71
|
+
raise typer.Exit(1)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@app.command("create-virtual")
|
|
75
|
+
def create_virtual_resource(
|
|
76
|
+
name: str = typer.Option(..., "--name", "-n", help="Resource name"),
|
|
77
|
+
integration_id: str = typer.Option(..., "--integration", "-i", help="Virtual integration ID"),
|
|
78
|
+
source_role_id: str = typer.Option(..., "--source-role-id", "-r", help="Source role ID from the linked integration"),
|
|
79
|
+
role_name: str = typer.Option("Start Session", "--role-name", help="Role name in the virtual integration"),
|
|
80
|
+
) -> None:
|
|
81
|
+
"""Create a resource in a virtual integration.
|
|
82
|
+
|
|
83
|
+
Virtual integrations link resources from other integrations. This command
|
|
84
|
+
creates a new resource that maps to a role from another integration.
|
|
85
|
+
|
|
86
|
+
Example:
|
|
87
|
+
bt entitle resources create-virtual \\
|
|
88
|
+
--name "Customer-05 (Bing7)" \\
|
|
89
|
+
--integration 22f4960f-b2ee-435f-9fa2-b82baeca06b2 \\
|
|
90
|
+
--source-role-id f0d60f41-be50-4402-b474-56e581ff7f50
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
with get_client() as client:
|
|
94
|
+
result = client.create_virtual_resource(
|
|
95
|
+
integration_id=integration_id,
|
|
96
|
+
name=name,
|
|
97
|
+
source_role_id=source_role_id,
|
|
98
|
+
role_name=role_name,
|
|
99
|
+
)
|
|
100
|
+
resource = result.get("result", result)
|
|
101
|
+
console.print(f"Created virtual resource: {resource.get('name')} (ID: {resource.get('id')})")
|
|
102
|
+
print_json(result)
|
|
103
|
+
except httpx.HTTPStatusError as e:
|
|
104
|
+
print_api_error(e, "create virtual resource")
|
|
105
|
+
raise typer.Exit(1)
|
|
106
|
+
except httpx.RequestError as e:
|
|
107
|
+
print_api_error(e, "create virtual resource")
|
|
108
|
+
raise typer.Exit(1)
|
|
109
|
+
except Exception as e:
|
|
110
|
+
print_api_error(e, "create virtual resource")
|
|
111
|
+
raise typer.Exit(1)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@app.command("delete")
|
|
115
|
+
def delete_resource(
|
|
116
|
+
resource_id: str = typer.Argument(..., help="Resource ID"),
|
|
117
|
+
) -> None:
|
|
118
|
+
"""Delete a resource by ID."""
|
|
119
|
+
try:
|
|
120
|
+
with get_client() as client:
|
|
121
|
+
client.delete_resource(resource_id)
|
|
122
|
+
console.print(f"Deleted resource: {resource_id}")
|
|
123
|
+
except httpx.HTTPStatusError as e:
|
|
124
|
+
print_api_error(e, "delete resource")
|
|
125
|
+
raise typer.Exit(1)
|
|
126
|
+
except httpx.RequestError as e:
|
|
127
|
+
print_api_error(e, "delete resource")
|
|
128
|
+
raise typer.Exit(1)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
print_api_error(e, "delete resource")
|
|
131
|
+
raise typer.Exit(1)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Role commands for Entitle."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ..client.base import get_client
|
|
9
|
+
from ...core.output import console, print_table, print_json, print_error, print_api_error
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True, help="Manage roles")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@app.command("list")
|
|
15
|
+
def list_roles(
|
|
16
|
+
resource_id: str = typer.Option(..., "--resource", "-r", help="Resource ID (required)"),
|
|
17
|
+
search: Optional[str] = typer.Option(None, "--search", "-s", help="Search filter"),
|
|
18
|
+
limit: int = typer.Option(100, "--limit", "-l", help="Maximum results to return"),
|
|
19
|
+
output: str = typer.Option("table", "--output", "-o", help="Output format: table, json"),
|
|
20
|
+
) -> None:
|
|
21
|
+
"""List roles for a resource.
|
|
22
|
+
|
|
23
|
+
The Entitle API requires a resource ID to list roles.
|
|
24
|
+
Use 'bt entitle resources list -i <integration_id>' to find resource IDs.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
bt entitle roles list -r <resource_id>
|
|
28
|
+
bt entitle roles list -r <resource_id> -s "admin"
|
|
29
|
+
"""
|
|
30
|
+
try:
|
|
31
|
+
with get_client() as client:
|
|
32
|
+
data = client.list_roles(
|
|
33
|
+
resource_id=resource_id,
|
|
34
|
+
search=search,
|
|
35
|
+
limit=limit,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
if output == "json":
|
|
39
|
+
print_json(data)
|
|
40
|
+
else:
|
|
41
|
+
print_table(
|
|
42
|
+
data,
|
|
43
|
+
[("ID", "id"), ("Name", "name"), ("Resource", "resource"), ("Requestable", "requestable")],
|
|
44
|
+
title="Roles",
|
|
45
|
+
)
|
|
46
|
+
except httpx.HTTPStatusError as e:
|
|
47
|
+
print_api_error(e, "list roles")
|
|
48
|
+
raise typer.Exit(1)
|
|
49
|
+
except httpx.RequestError as e:
|
|
50
|
+
print_api_error(e, "list roles")
|
|
51
|
+
raise typer.Exit(1)
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print_api_error(e, "list roles")
|
|
54
|
+
raise typer.Exit(1)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@app.command("get")
|
|
58
|
+
def get_role(
|
|
59
|
+
role_id: str = typer.Argument(..., help="Role ID"),
|
|
60
|
+
) -> None:
|
|
61
|
+
"""Get a role by ID."""
|
|
62
|
+
try:
|
|
63
|
+
with get_client() as client:
|
|
64
|
+
data = client.get_role(role_id)
|
|
65
|
+
print_json(data)
|
|
66
|
+
except httpx.HTTPStatusError as e:
|
|
67
|
+
print_api_error(e, "get role")
|
|
68
|
+
raise typer.Exit(1)
|
|
69
|
+
except httpx.RequestError as e:
|
|
70
|
+
print_api_error(e, "get role")
|
|
71
|
+
raise typer.Exit(1)
|
|
72
|
+
except Exception as e:
|
|
73
|
+
print_api_error(e, "get role")
|
|
74
|
+
raise typer.Exit(1)
|