pltr-cli 0.5.0__py3-none-any.whl → 0.5.2__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.
@@ -0,0 +1,454 @@
1
+ """
2
+ Resource Role management commands for Foundry filesystem.
3
+ """
4
+
5
+ import typer
6
+ from typing import Optional, List
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+
10
+ from ..services.resource_role import ResourceRoleService
11
+ from ..utils.formatting import OutputFormatter
12
+ from ..utils.progress import SpinnerProgressTracker
13
+ from ..auth.base import ProfileNotFoundError, MissingCredentialsError
14
+ from ..utils.completion import (
15
+ complete_rid,
16
+ complete_profile,
17
+ complete_output_format,
18
+ cache_rid,
19
+ )
20
+
21
+ app = typer.Typer()
22
+ console = Console()
23
+ formatter = OutputFormatter(console)
24
+
25
+
26
+ @app.command("grant")
27
+ def grant_role(
28
+ resource_rid: str = typer.Argument(
29
+ ..., help="Resource Identifier", autocompletion=complete_rid
30
+ ),
31
+ principal_id: str = typer.Option(
32
+ ..., "--principal-id", "-p", help="Principal (user/group) identifier"
33
+ ),
34
+ principal_type: str = typer.Option(
35
+ ...,
36
+ "--principal-type",
37
+ "-t",
38
+ help="Principal type (User or Group)",
39
+ ),
40
+ role_name: str = typer.Option(..., "--role", "-r", help="Role name to grant"),
41
+ profile: Optional[str] = typer.Option(
42
+ None, "--profile", help="Profile name", autocompletion=complete_profile
43
+ ),
44
+ format: str = typer.Option(
45
+ "table",
46
+ "--format",
47
+ "-f",
48
+ help="Output format (table, json, csv)",
49
+ autocompletion=complete_output_format,
50
+ ),
51
+ ):
52
+ """Grant a role to a principal on a resource."""
53
+ try:
54
+ service = ResourceRoleService(profile=profile)
55
+
56
+ with SpinnerProgressTracker().track_spinner(
57
+ f"Granting role '{role_name}' to {principal_type} '{principal_id}' on {resource_rid}..."
58
+ ):
59
+ role_grant = service.grant_role(
60
+ resource_rid=resource_rid,
61
+ principal_id=principal_id,
62
+ principal_type=principal_type.title(),
63
+ role_name=role_name,
64
+ )
65
+
66
+ formatter.print_success(
67
+ f"Successfully granted role '{role_name}' to {principal_type} '{principal_id}'"
68
+ )
69
+
70
+ # Format output
71
+ if format == "json":
72
+ formatter.format_dict(role_grant)
73
+ elif format == "csv":
74
+ formatter.format_list([role_grant])
75
+ else:
76
+ _format_role_grant_table(role_grant)
77
+
78
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
79
+ formatter.print_error(f"Authentication error: {e}")
80
+ raise typer.Exit(1)
81
+ except Exception as e:
82
+ formatter.print_error(f"Failed to grant role: {e}")
83
+ raise typer.Exit(1)
84
+
85
+
86
+ @app.command("revoke")
87
+ def revoke_role(
88
+ resource_rid: str = typer.Argument(
89
+ ..., help="Resource Identifier", autocompletion=complete_rid
90
+ ),
91
+ principal_id: str = typer.Option(
92
+ ..., "--principal-id", "-p", help="Principal (user/group) identifier"
93
+ ),
94
+ principal_type: str = typer.Option(
95
+ ...,
96
+ "--principal-type",
97
+ "-t",
98
+ help="Principal type (User or Group)",
99
+ ),
100
+ role_name: str = typer.Option(..., "--role", "-r", help="Role name to revoke"),
101
+ profile: Optional[str] = typer.Option(
102
+ None, "--profile", help="Profile name", autocompletion=complete_profile
103
+ ),
104
+ confirm: bool = typer.Option(False, "--confirm", help="Skip confirmation prompt"),
105
+ ):
106
+ """Revoke a role from a principal on a resource."""
107
+ try:
108
+ if not confirm:
109
+ confirm_revoke = typer.confirm(
110
+ f"Are you sure you want to revoke role '{role_name}' from {principal_type} '{principal_id}' on resource {resource_rid}?"
111
+ )
112
+ if not confirm_revoke:
113
+ formatter.print_info("Role revocation cancelled.")
114
+ return
115
+
116
+ service = ResourceRoleService(profile=profile)
117
+
118
+ with SpinnerProgressTracker().track_spinner(
119
+ f"Revoking role '{role_name}' from {principal_type} '{principal_id}' on {resource_rid}..."
120
+ ):
121
+ service.revoke_role(
122
+ resource_rid=resource_rid,
123
+ principal_id=principal_id,
124
+ principal_type=principal_type.title(),
125
+ role_name=role_name,
126
+ )
127
+
128
+ formatter.print_success(
129
+ f"Successfully revoked role '{role_name}' from {principal_type} '{principal_id}'"
130
+ )
131
+
132
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
133
+ formatter.print_error(f"Authentication error: {e}")
134
+ raise typer.Exit(1)
135
+ except Exception as e:
136
+ formatter.print_error(f"Failed to revoke role: {e}")
137
+ raise typer.Exit(1)
138
+
139
+
140
+ @app.command("list")
141
+ def list_resource_roles(
142
+ resource_rid: str = typer.Argument(
143
+ ..., help="Resource Identifier", autocompletion=complete_rid
144
+ ),
145
+ principal_type: Optional[str] = typer.Option(
146
+ None,
147
+ "--principal-type",
148
+ "-t",
149
+ help="Filter by principal type (User or Group)",
150
+ ),
151
+ profile: Optional[str] = typer.Option(
152
+ None, "--profile", help="Profile name", autocompletion=complete_profile
153
+ ),
154
+ format: str = typer.Option(
155
+ "table",
156
+ "--format",
157
+ "-f",
158
+ help="Output format (table, json, csv)",
159
+ autocompletion=complete_output_format,
160
+ ),
161
+ output: Optional[str] = typer.Option(
162
+ None, "--output", "-o", help="Output file path"
163
+ ),
164
+ page_size: Optional[int] = typer.Option(
165
+ None, "--page-size", help="Number of items per page"
166
+ ),
167
+ ):
168
+ """List all roles granted on a resource."""
169
+ try:
170
+ # Cache the RID for future completions
171
+ cache_rid(resource_rid)
172
+
173
+ service = ResourceRoleService(profile=profile)
174
+
175
+ filter_desc = f" for {principal_type}s" if principal_type else ""
176
+ with SpinnerProgressTracker().track_spinner(
177
+ f"Listing roles on {resource_rid}{filter_desc}..."
178
+ ):
179
+ role_grants = service.list_resource_roles(
180
+ resource_rid=resource_rid,
181
+ principal_type=principal_type.title() if principal_type else None,
182
+ page_size=page_size,
183
+ )
184
+
185
+ if not role_grants:
186
+ formatter.print_info(f"No roles found on resource {resource_rid}.")
187
+ return
188
+
189
+ # Format output
190
+ if format == "json":
191
+ if output:
192
+ formatter.save_to_file(role_grants, output, "json")
193
+ else:
194
+ formatter.format_list(role_grants)
195
+ elif format == "csv":
196
+ if output:
197
+ formatter.save_to_file(role_grants, output, "csv")
198
+ else:
199
+ formatter.format_list(role_grants)
200
+ else:
201
+ _format_role_grants_table(role_grants)
202
+
203
+ if output:
204
+ formatter.print_success(f"Role grants saved to {output}")
205
+
206
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
207
+ formatter.print_error(f"Authentication error: {e}")
208
+ raise typer.Exit(1)
209
+ except Exception as e:
210
+ formatter.print_error(f"Failed to list resource roles: {e}")
211
+ raise typer.Exit(1)
212
+
213
+
214
+ @app.command("get-principal-roles")
215
+ def get_principal_roles(
216
+ principal_id: str = typer.Option(
217
+ ..., "--principal-id", "-p", help="Principal (user/group) identifier"
218
+ ),
219
+ principal_type: str = typer.Option(
220
+ ...,
221
+ "--principal-type",
222
+ "-t",
223
+ help="Principal type (User or Group)",
224
+ ),
225
+ resource_rid: Optional[str] = typer.Option(
226
+ None,
227
+ "--resource-rid",
228
+ "-r",
229
+ help="Filter by resource RID",
230
+ autocompletion=complete_rid,
231
+ ),
232
+ profile: Optional[str] = typer.Option(
233
+ None, "--profile", help="Profile name", autocompletion=complete_profile
234
+ ),
235
+ format: str = typer.Option(
236
+ "table",
237
+ "--format",
238
+ "-f",
239
+ help="Output format (table, json, csv)",
240
+ autocompletion=complete_output_format,
241
+ ),
242
+ output: Optional[str] = typer.Option(
243
+ None, "--output", "-o", help="Output file path"
244
+ ),
245
+ page_size: Optional[int] = typer.Option(
246
+ None, "--page-size", help="Number of items per page"
247
+ ),
248
+ ):
249
+ """Get all roles granted to a principal, optionally filtered by resource."""
250
+ try:
251
+ service = ResourceRoleService(profile=profile)
252
+
253
+ filter_desc = f" on resource {resource_rid}" if resource_rid else ""
254
+ with SpinnerProgressTracker().track_spinner(
255
+ f"Getting roles for {principal_type} '{principal_id}'{filter_desc}..."
256
+ ):
257
+ role_grants = service.get_principal_roles(
258
+ principal_id=principal_id,
259
+ principal_type=principal_type.title(),
260
+ resource_rid=resource_rid,
261
+ page_size=page_size,
262
+ )
263
+
264
+ if not role_grants:
265
+ formatter.print_info(
266
+ f"No roles found for {principal_type} '{principal_id}'."
267
+ )
268
+ return
269
+
270
+ # Format output
271
+ if format == "json":
272
+ if output:
273
+ formatter.save_to_file(role_grants, output, "json")
274
+ else:
275
+ formatter.format_list(role_grants)
276
+ elif format == "csv":
277
+ if output:
278
+ formatter.save_to_file(role_grants, output, "csv")
279
+ else:
280
+ formatter.format_list(role_grants)
281
+ else:
282
+ _format_role_grants_table(role_grants)
283
+
284
+ if output:
285
+ formatter.print_success(f"Role grants saved to {output}")
286
+
287
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
288
+ formatter.print_error(f"Authentication error: {e}")
289
+ raise typer.Exit(1)
290
+ except Exception as e:
291
+ formatter.print_error(f"Failed to get principal roles: {e}")
292
+ raise typer.Exit(1)
293
+
294
+
295
+ @app.command("get-available-roles")
296
+ def get_available_roles(
297
+ resource_rid: str = typer.Argument(
298
+ ..., help="Resource Identifier", autocompletion=complete_rid
299
+ ),
300
+ profile: Optional[str] = typer.Option(
301
+ None, "--profile", help="Profile name", autocompletion=complete_profile
302
+ ),
303
+ format: str = typer.Option(
304
+ "table",
305
+ "--format",
306
+ "-f",
307
+ help="Output format (table, json, csv)",
308
+ autocompletion=complete_output_format,
309
+ ),
310
+ output: Optional[str] = typer.Option(
311
+ None, "--output", "-o", help="Output file path"
312
+ ),
313
+ ):
314
+ """Get all available roles for a resource type."""
315
+ try:
316
+ service = ResourceRoleService(profile=profile)
317
+
318
+ with SpinnerProgressTracker().track_spinner(
319
+ f"Getting available roles for {resource_rid}..."
320
+ ):
321
+ roles = service.get_available_roles(resource_rid)
322
+
323
+ if not roles:
324
+ formatter.print_info(
325
+ f"No available roles found for resource {resource_rid}."
326
+ )
327
+ return
328
+
329
+ # Format output
330
+ if format == "json":
331
+ if output:
332
+ formatter.save_to_file(roles, output, "json")
333
+ else:
334
+ formatter.format_list(roles)
335
+ elif format == "csv":
336
+ if output:
337
+ formatter.save_to_file(roles, output, "csv")
338
+ else:
339
+ formatter.format_list(roles)
340
+ else:
341
+ _format_available_roles_table(roles)
342
+
343
+ if output:
344
+ formatter.print_success(f"Available roles saved to {output}")
345
+
346
+ except (ProfileNotFoundError, MissingCredentialsError) as e:
347
+ formatter.print_error(f"Authentication error: {e}")
348
+ raise typer.Exit(1)
349
+ except Exception as e:
350
+ formatter.print_error(f"Failed to get available roles: {e}")
351
+ raise typer.Exit(1)
352
+
353
+
354
+ def _format_role_grant_table(role_grant: dict):
355
+ """Format role grant information as a table."""
356
+ table = Table(
357
+ title="Role Grant Information", show_header=True, header_style="bold cyan"
358
+ )
359
+ table.add_column("Property", style="cyan")
360
+ table.add_column("Value")
361
+
362
+ table.add_row("Resource RID", role_grant.get("resource_rid", "N/A"))
363
+ table.add_row("Principal ID", role_grant.get("principal_id", "N/A"))
364
+ table.add_row("Principal Type", role_grant.get("principal_type", "N/A"))
365
+ table.add_row("Role Name", role_grant.get("role_name", "N/A"))
366
+ table.add_row("Granted By", role_grant.get("granted_by", "N/A"))
367
+ table.add_row("Granted Time", role_grant.get("granted_time", "N/A"))
368
+ table.add_row("Expires At", role_grant.get("expires_at", "N/A"))
369
+
370
+ console.print(table)
371
+
372
+
373
+ def _format_role_grants_table(role_grants: List[dict]):
374
+ """Format multiple role grants as a table."""
375
+ table = Table(title="Role Grants", show_header=True, header_style="bold cyan")
376
+ table.add_column("Principal Type")
377
+ table.add_column("Principal ID")
378
+ table.add_column("Role Name")
379
+ table.add_column("Granted By")
380
+ table.add_column("Granted Time")
381
+
382
+ for grant in role_grants:
383
+ table.add_row(
384
+ grant.get("principal_type", "N/A"),
385
+ grant.get("principal_id", "N/A"),
386
+ grant.get("role_name", "N/A"),
387
+ grant.get("granted_by", "N/A"),
388
+ grant.get("granted_time", "N/A"),
389
+ )
390
+
391
+ console.print(table)
392
+ console.print(f"\nTotal: {len(role_grants)} role grants")
393
+
394
+
395
+ def _format_available_roles_table(roles: List[dict]):
396
+ """Format available roles as a table."""
397
+ table = Table(title="Available Roles", show_header=True, header_style="bold cyan")
398
+ table.add_column("Name")
399
+ table.add_column("Display Name")
400
+ table.add_column("Description")
401
+ table.add_column("Owner-like")
402
+
403
+ for role in roles:
404
+ table.add_row(
405
+ role.get("name", "N/A"),
406
+ role.get("display_name", "N/A"),
407
+ role.get("description", "N/A"),
408
+ "Yes" if role.get("is_owner_like", False) else "No",
409
+ )
410
+
411
+ console.print(table)
412
+ console.print(f"\nTotal: {len(roles)} available roles")
413
+
414
+
415
+ @app.callback()
416
+ def main():
417
+ """
418
+ Resource Role operations using foundry-platform-sdk.
419
+
420
+ Manage permissions and access control for resources in the Foundry filesystem.
421
+ Grant and revoke roles for users and groups on specific resources.
422
+
423
+ Examples:
424
+ # Grant a role to a user
425
+ pltr resource-role grant ri.compass.main.dataset.xyz123 \\
426
+ --principal-id user123 --principal-type User --role viewer
427
+
428
+ # Grant a role to a group
429
+ pltr resource-role grant ri.compass.main.project.abc456 \\
430
+ --principal-id group789 --principal-type Group --role editor
431
+
432
+ # List all roles on a resource
433
+ pltr resource-role list ri.compass.main.dataset.xyz123
434
+
435
+ # List only user roles on a resource
436
+ pltr resource-role list ri.compass.main.dataset.xyz123 --principal-type User
437
+
438
+ # Get all roles for a specific user
439
+ pltr resource-role get-principal-roles \\
440
+ --principal-id user123 --principal-type User
441
+
442
+ # Get roles for a user on a specific resource
443
+ pltr resource-role get-principal-roles \\
444
+ --principal-id user123 --principal-type User \\
445
+ --resource-rid ri.compass.main.dataset.xyz123
446
+
447
+ # See what roles are available for a resource
448
+ pltr resource-role get-available-roles ri.compass.main.dataset.xyz123
449
+
450
+ # Revoke a role from a user
451
+ pltr resource-role revoke ri.compass.main.dataset.xyz123 \\
452
+ --principal-id user123 --principal-type User --role viewer
453
+ """
454
+ pass