systemlink-cli 1.3.1__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.
Files changed (74) hide show
  1. slcli/__init__.py +1 -0
  2. slcli/__main__.py +23 -0
  3. slcli/_version.py +4 -0
  4. slcli/asset_click.py +1289 -0
  5. slcli/cli_formatters.py +218 -0
  6. slcli/cli_utils.py +504 -0
  7. slcli/comment_click.py +602 -0
  8. slcli/completion_click.py +418 -0
  9. slcli/config.py +81 -0
  10. slcli/config_click.py +498 -0
  11. slcli/dff_click.py +979 -0
  12. slcli/dff_decorators.py +24 -0
  13. slcli/example_click.py +404 -0
  14. slcli/example_loader.py +274 -0
  15. slcli/example_provisioner.py +2777 -0
  16. slcli/examples/README.md +134 -0
  17. slcli/examples/_schema/schema-v1.0.json +169 -0
  18. slcli/examples/demo-complete-workflow/README.md +323 -0
  19. slcli/examples/demo-complete-workflow/config.yaml +638 -0
  20. slcli/examples/demo-test-plans/README.md +132 -0
  21. slcli/examples/demo-test-plans/config.yaml +154 -0
  22. slcli/examples/exercise-5-1-parametric-insights/README.md +101 -0
  23. slcli/examples/exercise-5-1-parametric-insights/config.yaml +1589 -0
  24. slcli/examples/exercise-7-1-test-plans/README.md +93 -0
  25. slcli/examples/exercise-7-1-test-plans/config.yaml +323 -0
  26. slcli/examples/spec-compliance-notebooks/README.md +140 -0
  27. slcli/examples/spec-compliance-notebooks/config.yaml +112 -0
  28. slcli/examples/spec-compliance-notebooks/notebooks/SpecAnalysis_ComplianceCalculation.ipynb +1553 -0
  29. slcli/examples/spec-compliance-notebooks/notebooks/SpecComplianceCalculation.ipynb +1577 -0
  30. slcli/examples/spec-compliance-notebooks/notebooks/SpecfileExtractionAndIngestion.ipynb +912 -0
  31. slcli/examples/spec-compliance-notebooks/spec_template.xlsx +0 -0
  32. slcli/feed_click.py +892 -0
  33. slcli/file_click.py +932 -0
  34. slcli/function_click.py +1400 -0
  35. slcli/function_templates.py +85 -0
  36. slcli/main.py +406 -0
  37. slcli/mcp_click.py +269 -0
  38. slcli/mcp_server.py +748 -0
  39. slcli/notebook_click.py +1770 -0
  40. slcli/platform.py +345 -0
  41. slcli/policy_click.py +679 -0
  42. slcli/policy_utils.py +411 -0
  43. slcli/profiles.py +411 -0
  44. slcli/response_handlers.py +359 -0
  45. slcli/routine_click.py +763 -0
  46. slcli/skill_click.py +253 -0
  47. slcli/skills/slcli/SKILL.md +713 -0
  48. slcli/skills/slcli/references/analysis-recipes.md +474 -0
  49. slcli/skills/slcli/references/filtering.md +236 -0
  50. slcli/skills/systemlink-webapp/SKILL.md +744 -0
  51. slcli/skills/systemlink-webapp/references/deployment.md +123 -0
  52. slcli/skills/systemlink-webapp/references/nimble-angular.md +380 -0
  53. slcli/skills/systemlink-webapp/references/systemlink-services.md +192 -0
  54. slcli/ssl_trust.py +93 -0
  55. slcli/system_click.py +2216 -0
  56. slcli/table_utils.py +124 -0
  57. slcli/tag_click.py +794 -0
  58. slcli/templates_click.py +599 -0
  59. slcli/testmonitor_click.py +1667 -0
  60. slcli/universal_handlers.py +305 -0
  61. slcli/user_click.py +1218 -0
  62. slcli/utils.py +832 -0
  63. slcli/web_editor.py +295 -0
  64. slcli/webapp_click.py +981 -0
  65. slcli/workflow_preview.py +287 -0
  66. slcli/workflows_click.py +988 -0
  67. slcli/workitem_click.py +2258 -0
  68. slcli/workspace_click.py +576 -0
  69. slcli/workspace_utils.py +206 -0
  70. systemlink_cli-1.3.1.dist-info/METADATA +20 -0
  71. systemlink_cli-1.3.1.dist-info/RECORD +74 -0
  72. systemlink_cli-1.3.1.dist-info/WHEEL +4 -0
  73. systemlink_cli-1.3.1.dist-info/entry_points.txt +7 -0
  74. systemlink_cli-1.3.1.dist-info/licenses/LICENSE +21 -0
slcli/policy_utils.py ADDED
@@ -0,0 +1,411 @@
1
+ """Utility functions for auth policy management.
2
+
3
+ Provides helper functions for policy and policy template operations,
4
+ including formatting, validation, and API interaction helpers.
5
+ """
6
+
7
+ from typing import Any, Dict, List, Optional, Tuple
8
+
9
+ import click
10
+
11
+ from .utils import get_base_url, make_api_request
12
+
13
+
14
+ def _fetch_policy_details(policy_id: str, handle_errors: bool = True) -> Optional[Dict[str, Any]]:
15
+ """Fetch policy details from the Auth service.
16
+
17
+ Args:
18
+ policy_id: The policy ID to fetch
19
+ handle_errors: Whether to raise exceptions on API errors
20
+
21
+ Returns:
22
+ Policy details dictionary, or None if not found/no permission
23
+ """
24
+ try:
25
+ url = f"{get_base_url()}/niauth/v1/policies/{policy_id}"
26
+ resp = make_api_request("GET", url, payload=None, handle_errors=handle_errors)
27
+ return resp.json()
28
+ except Exception:
29
+ return None
30
+
31
+
32
+ def _fetch_template_details(
33
+ template_id: str, handle_errors: bool = True
34
+ ) -> Optional[Dict[str, Any]]:
35
+ """Fetch policy template details from the Auth service.
36
+
37
+ Args:
38
+ template_id: The policy template ID to fetch
39
+ handle_errors: Whether to raise exceptions on API errors
40
+
41
+ Returns:
42
+ Policy template details dictionary, or None if not found/no permission
43
+ """
44
+ try:
45
+ url = f"{get_base_url()}/niauth/v1/policy-templates/{template_id}"
46
+ resp = make_api_request("GET", url, payload=None, handle_errors=handle_errors)
47
+ return resp.json()
48
+ except Exception:
49
+ return None
50
+
51
+
52
+ def _format_statements_for_display(statements: List[Dict[str, Any]]) -> str:
53
+ """Format statements in a readable way for display.
54
+
55
+ Args:
56
+ statements: List of statement dictionaries from API
57
+
58
+ Returns:
59
+ Formatted string representation
60
+ """
61
+ if not statements:
62
+ return "No statements"
63
+
64
+ lines: List[str] = []
65
+ for i, statement in enumerate(statements, 1):
66
+ lines.append(f"\nStatement {i}:")
67
+
68
+ workspace = statement.get("workspace", "N/A")
69
+ lines.append(f" Workspace: {workspace}")
70
+
71
+ # Format actions
72
+ actions = statement.get("actions", [])
73
+ if actions:
74
+ lines.append(f" Actions ({len(actions)}):")
75
+ for action in actions:
76
+ lines.append(f" • {action}")
77
+
78
+ # Format resources
79
+ resources = statement.get("resource", [])
80
+ if resources:
81
+ lines.append(f" Resources ({len(resources)}):")
82
+ for resource in resources:
83
+ lines.append(f" • {resource}")
84
+
85
+ # Format description if present
86
+ description = statement.get("description")
87
+ if description:
88
+ lines.append(f" Description: {description}")
89
+
90
+ return "\n".join(lines)
91
+
92
+
93
+ def _validate_statements(statements: List[Dict[str, Any]]) -> Tuple[bool, Optional[str]]:
94
+ """Validate statement structure.
95
+
96
+ Args:
97
+ statements: List of statement dictionaries to validate
98
+
99
+ Returns:
100
+ Tuple of (is_valid, error_message)
101
+ """
102
+ if not statements:
103
+ return False, "At least one statement is required"
104
+
105
+ for i, stmt in enumerate(statements):
106
+ # Validate stmt is a dictionary
107
+ is_dict: bool = isinstance(stmt, dict)
108
+ if not is_dict:
109
+ return False, f"Statement {i + 1} is not a dictionary"
110
+
111
+ # Check required fields exist and have correct types
112
+ actions: Any = stmt.get("actions")
113
+ resources: Any = stmt.get("resource")
114
+ workspace: Any = stmt.get("workspace")
115
+
116
+ # Validate actions field
117
+ is_actions_list: bool = isinstance(actions, list)
118
+ if not is_actions_list:
119
+ return False, f"Statement {i + 1}: 'actions' must be a list"
120
+ if not actions: # Empty list check
121
+ return False, f"Statement {i + 1}: 'actions' must not be empty"
122
+
123
+ # Validate resources field
124
+ is_resources_list: bool = isinstance(resources, list)
125
+ if not is_resources_list:
126
+ return False, f"Statement {i + 1}: 'resource' must be a list"
127
+ if not resources: # Empty list check
128
+ return False, f"Statement {i + 1}: 'resource' must not be empty"
129
+
130
+ # Validate workspace field
131
+ is_workspace_str: bool = isinstance(workspace, str)
132
+ if not is_workspace_str:
133
+ return False, f"Statement {i + 1}: 'workspace' must be a string"
134
+ if not workspace: # Empty string check
135
+ return False, f"Statement {i + 1}: 'workspace' must not be empty"
136
+
137
+ return True, None
138
+
139
+
140
+ def _format_policy_list_row(policy: Dict[str, Any]) -> List[str]:
141
+ """Format a policy for table list output.
142
+
143
+ Args:
144
+ policy: Policy dictionary from API
145
+
146
+ Returns:
147
+ List of formatted column values
148
+ """
149
+ policy_id = policy.get("id", "N/A")
150
+ name = policy.get("name", "N/A")
151
+ policy_type = policy.get("type", "N/A")
152
+ is_builtin = policy.get("builtIn", False)
153
+ builtin_str = "Yes" if is_builtin else "No"
154
+
155
+ # Count statements (or show "inherited" if template-based)
156
+ template_id = policy.get("templateId")
157
+ if template_id:
158
+ statement_count = "(inherited)"
159
+ else:
160
+ statements = policy.get("statements", [])
161
+ statement_count = str(len(statements))
162
+
163
+ return [policy_id, name, policy_type, builtin_str, statement_count]
164
+
165
+
166
+ def _format_template_list_row(template: Dict[str, Any]) -> List[str]:
167
+ """Format a template for table list output.
168
+
169
+ Args:
170
+ template: Policy template dictionary from API
171
+
172
+ Returns:
173
+ List of formatted column values
174
+ """
175
+ template_id = template.get("id", "N/A")
176
+ name = template.get("name", "N/A")
177
+ template_type = template.get("type", "N/A")
178
+ is_builtin = template.get("builtIn", False)
179
+ builtin_str = "Yes" if is_builtin else "No"
180
+
181
+ statements = template.get("statements", [])
182
+ statement_count = str(len(statements))
183
+
184
+ return [template_id, name, template_type, builtin_str, statement_count]
185
+
186
+
187
+ def _parse_properties_from_cli(properties: tuple) -> Dict[str, str]:
188
+ """Parse key=value properties from CLI arguments.
189
+
190
+ Args:
191
+ properties: Tuple of "key=value" strings
192
+
193
+ Returns:
194
+ Dictionary of parsed properties
195
+
196
+ Raises:
197
+ ValueError: If format is invalid
198
+ """
199
+ props_dict: Dict[str, str] = {}
200
+ for prop in properties:
201
+ if "=" not in prop:
202
+ raise ValueError(f"Invalid property format: {prop}. Use key=value")
203
+ key, val = prop.split("=", 1)
204
+ props_dict[key.strip()] = val.strip()
205
+ return props_dict
206
+
207
+
208
+ def _load_statements_from_file(file_path: str) -> List[Dict[str, Any]]:
209
+ """Load statements from a JSON file.
210
+
211
+ Args:
212
+ file_path: Path to JSON file containing statements
213
+
214
+ Returns:
215
+ List of statement dictionaries
216
+
217
+ Raises:
218
+ ValueError: If file cannot be read or parsed
219
+ """
220
+ import json
221
+
222
+ try:
223
+ with open(file_path, "r") as f:
224
+ data = json.load(f)
225
+ except FileNotFoundError:
226
+ raise ValueError(f"File not found: {file_path}")
227
+ except json.JSONDecodeError as e:
228
+ raise ValueError(f"Invalid JSON in file: {e}")
229
+
230
+ # Support both direct statements list or wrapped in "statements" key
231
+ if isinstance(data, list):
232
+ return data
233
+ elif isinstance(data, dict) and "statements" in data:
234
+ statements = data["statements"]
235
+ if not isinstance(statements, list):
236
+ raise ValueError('"statements" field must be a list')
237
+ return statements
238
+ else:
239
+ raise ValueError('File must contain a list of statements or a "statements" key')
240
+
241
+
242
+ def _build_policy_payload(
243
+ name: str,
244
+ policy_type: str,
245
+ statements: Optional[List[Dict[str, Any]]] = None,
246
+ template_id: Optional[str] = None,
247
+ workspace: Optional[str] = None,
248
+ properties: Optional[Dict[str, str]] = None,
249
+ ) -> Dict[str, Any]:
250
+ """Build a policy creation/update payload.
251
+
252
+ Args:
253
+ name: Policy name
254
+ policy_type: Policy type (default|internal|custom|role)
255
+ statements: List of statement dictionaries (optional if template_id used)
256
+ template_id: Policy template ID (optional)
257
+ workspace: Workspace ID (required if template_id used)
258
+ properties: Custom properties dictionary
259
+
260
+ Returns:
261
+ Policy payload for API request
262
+
263
+ Raises:
264
+ ValueError: If required fields are missing
265
+ """
266
+ payload: Dict[str, Any] = {
267
+ "name": name,
268
+ "type": policy_type,
269
+ }
270
+
271
+ if template_id:
272
+ if not workspace:
273
+ raise ValueError("workspace is required when using a template")
274
+ payload["templateId"] = template_id
275
+ payload["workspace"] = workspace
276
+ else:
277
+ if not statements:
278
+ raise ValueError("statements are required if template_id is not used")
279
+ is_valid, error_msg = _validate_statements(statements)
280
+ if not is_valid:
281
+ raise ValueError(error_msg)
282
+ payload["statements"] = statements
283
+
284
+ if properties:
285
+ payload["properties"] = properties
286
+
287
+ return payload
288
+
289
+
290
+ def _build_template_payload(
291
+ name: str,
292
+ template_type: str,
293
+ statements: Optional[List[Dict[str, Any]]] = None,
294
+ properties: Optional[Dict[str, str]] = None,
295
+ ) -> Dict[str, Any]:
296
+ """Build a policy template creation/update payload.
297
+
298
+ Args:
299
+ name: Template name
300
+ template_type: Template type (user|service)
301
+ statements: List of statement dictionaries
302
+ properties: Custom properties dictionary
303
+
304
+ Returns:
305
+ Template payload for API request
306
+
307
+ Raises:
308
+ ValueError: If required fields are missing
309
+ """
310
+ if not statements:
311
+ raise ValueError("statements are required for policy templates")
312
+
313
+ is_valid, error_msg = _validate_statements(statements)
314
+ if not is_valid:
315
+ raise ValueError(error_msg)
316
+
317
+ payload: Dict[str, Any] = {
318
+ "name": name,
319
+ "type": template_type,
320
+ "statements": statements,
321
+ }
322
+
323
+ if properties:
324
+ payload["properties"] = properties
325
+
326
+ return payload
327
+
328
+
329
+ def _display_policy_details(policy: Dict[str, Any], format_output: str = "table") -> None:
330
+ """Display detailed policy information.
331
+
332
+ Args:
333
+ policy: Policy dictionary from API
334
+ format_output: Output format (table or json)
335
+ """
336
+ import json
337
+
338
+ if format_output.lower() == "json":
339
+ click.echo(json.dumps(policy, indent=2))
340
+ return
341
+
342
+ # Table format
343
+ click.echo(f"\n✓ Policy: {policy.get('name', 'N/A')}")
344
+ click.echo("-" * 80)
345
+ click.echo(f" ID: {policy.get('id', 'N/A')}")
346
+ click.echo(f" Type: {policy.get('type', 'N/A')}")
347
+ click.echo(f" Built-in: {'Yes' if policy.get('builtIn') else 'No'}")
348
+ click.echo(f" Owner ID: {policy.get('userId', 'N/A')}")
349
+ click.echo(f" Created: {policy.get('created', 'N/A')}")
350
+ click.echo(f" Updated: {policy.get('updated', 'N/A')}")
351
+
352
+ # Show template reference if present
353
+ template_id = policy.get("templateId")
354
+ if template_id:
355
+ click.echo(f"\n Template-based Policy:")
356
+ click.echo(f" Template ID: {template_id}")
357
+ click.echo(f" Workspace: {policy.get('workspace', 'N/A')}")
358
+
359
+ # Show properties if present
360
+ properties = policy.get("properties", {})
361
+ if properties:
362
+ click.echo(f"\n Properties:")
363
+ for key, val in properties.items():
364
+ click.echo(f" {key}: {val}")
365
+
366
+ # Show statements
367
+ statements = policy.get("statements", [])
368
+ if statements:
369
+ click.echo(f"\n Statements ({len(statements)}):")
370
+ click.echo(_format_statements_for_display(statements))
371
+
372
+ click.echo()
373
+
374
+
375
+ def _display_template_details(template: Dict[str, Any], format_output: str = "table") -> None:
376
+ """Display detailed template information.
377
+
378
+ Args:
379
+ template: Policy template dictionary from API
380
+ format_output: Output format (table or json)
381
+ """
382
+ import json
383
+
384
+ if format_output.lower() == "json":
385
+ click.echo(json.dumps(template, indent=2))
386
+ return
387
+
388
+ # Table format
389
+ click.echo(f"\n✓ Policy Template: {template.get('name', 'N/A')}")
390
+ click.echo("-" * 80)
391
+ click.echo(f" ID: {template.get('id', 'N/A')}")
392
+ click.echo(f" Type: {template.get('type', 'N/A')}")
393
+ click.echo(f" Built-in: {'Yes' if template.get('builtIn') else 'No'}")
394
+ click.echo(f" Owner ID: {template.get('userId', 'N/A')}")
395
+ click.echo(f" Created: {template.get('created', 'N/A')}")
396
+ click.echo(f" Updated: {template.get('updated', 'N/A')}")
397
+
398
+ # Show properties if present
399
+ properties = template.get("properties", {})
400
+ if properties:
401
+ click.echo(f"\n Properties:")
402
+ for key, val in properties.items():
403
+ click.echo(f" {key}: {val}")
404
+
405
+ # Show statements
406
+ statements = template.get("statements", [])
407
+ if statements:
408
+ click.echo(f"\n Statements ({len(statements)}):")
409
+ click.echo(_format_statements_for_display(statements))
410
+
411
+ click.echo()