d365fo-client 0.1.0__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.
- d365fo_client/__init__.py +305 -0
- d365fo_client/auth.py +93 -0
- d365fo_client/cli.py +700 -0
- d365fo_client/client.py +1454 -0
- d365fo_client/config.py +304 -0
- d365fo_client/crud.py +200 -0
- d365fo_client/exceptions.py +49 -0
- d365fo_client/labels.py +528 -0
- d365fo_client/main.py +502 -0
- d365fo_client/mcp/__init__.py +16 -0
- d365fo_client/mcp/client_manager.py +276 -0
- d365fo_client/mcp/main.py +98 -0
- d365fo_client/mcp/models.py +371 -0
- d365fo_client/mcp/prompts/__init__.py +43 -0
- d365fo_client/mcp/prompts/action_execution.py +480 -0
- d365fo_client/mcp/prompts/sequence_analysis.py +349 -0
- d365fo_client/mcp/resources/__init__.py +15 -0
- d365fo_client/mcp/resources/database_handler.py +555 -0
- d365fo_client/mcp/resources/entity_handler.py +176 -0
- d365fo_client/mcp/resources/environment_handler.py +132 -0
- d365fo_client/mcp/resources/metadata_handler.py +283 -0
- d365fo_client/mcp/resources/query_handler.py +135 -0
- d365fo_client/mcp/server.py +432 -0
- d365fo_client/mcp/tools/__init__.py +17 -0
- d365fo_client/mcp/tools/connection_tools.py +175 -0
- d365fo_client/mcp/tools/crud_tools.py +579 -0
- d365fo_client/mcp/tools/database_tools.py +813 -0
- d365fo_client/mcp/tools/label_tools.py +189 -0
- d365fo_client/mcp/tools/metadata_tools.py +766 -0
- d365fo_client/mcp/tools/profile_tools.py +706 -0
- d365fo_client/metadata_api.py +793 -0
- d365fo_client/metadata_v2/__init__.py +59 -0
- d365fo_client/metadata_v2/cache_v2.py +1372 -0
- d365fo_client/metadata_v2/database_v2.py +585 -0
- d365fo_client/metadata_v2/global_version_manager.py +573 -0
- d365fo_client/metadata_v2/search_engine_v2.py +423 -0
- d365fo_client/metadata_v2/sync_manager_v2.py +819 -0
- d365fo_client/metadata_v2/version_detector.py +439 -0
- d365fo_client/models.py +862 -0
- d365fo_client/output.py +181 -0
- d365fo_client/profile_manager.py +342 -0
- d365fo_client/profiles.py +178 -0
- d365fo_client/query.py +162 -0
- d365fo_client/session.py +60 -0
- d365fo_client/utils.py +196 -0
- d365fo_client-0.1.0.dist-info/METADATA +1084 -0
- d365fo_client-0.1.0.dist-info/RECORD +51 -0
- d365fo_client-0.1.0.dist-info/WHEEL +5 -0
- d365fo_client-0.1.0.dist-info/entry_points.txt +3 -0
- d365fo_client-0.1.0.dist-info/licenses/LICENSE +21 -0
- d365fo_client-0.1.0.dist-info/top_level.txt +1 -0
d365fo_client/cli.py
ADDED
@@ -0,0 +1,700 @@
|
|
1
|
+
"""CLI manager for d365fo-client commands."""
|
2
|
+
|
3
|
+
import argparse
|
4
|
+
import asyncio
|
5
|
+
import json
|
6
|
+
import sys
|
7
|
+
from typing import Any, Dict, Optional
|
8
|
+
|
9
|
+
from .client import FOClient
|
10
|
+
from .config import ConfigManager
|
11
|
+
from .exceptions import FOClientError
|
12
|
+
from .models import FOClientConfig, QueryOptions
|
13
|
+
from .output import OutputFormatter, format_error_message, format_success_message
|
14
|
+
from .profiles import Profile
|
15
|
+
|
16
|
+
|
17
|
+
class CLIManager:
|
18
|
+
"""Main CLI command manager."""
|
19
|
+
|
20
|
+
def __init__(self):
|
21
|
+
"""Initialize CLI manager."""
|
22
|
+
self.config_manager = ConfigManager()
|
23
|
+
self.output_formatter = None
|
24
|
+
self.client = None
|
25
|
+
|
26
|
+
async def execute_command(self, args: argparse.Namespace) -> int:
|
27
|
+
"""Execute the specified command.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
args: Parsed command line arguments
|
31
|
+
|
32
|
+
Returns:
|
33
|
+
Exit code (0 for success, 1 for error)
|
34
|
+
"""
|
35
|
+
try:
|
36
|
+
# Setup output formatter
|
37
|
+
output_format = getattr(args, "output", "table")
|
38
|
+
self.output_formatter = OutputFormatter(output_format)
|
39
|
+
|
40
|
+
# Handle legacy demo mode first
|
41
|
+
if getattr(args, "demo", False):
|
42
|
+
return await self._run_demo()
|
43
|
+
|
44
|
+
# Handle configuration commands (no client needed)
|
45
|
+
if getattr(args, "command", None) == "config":
|
46
|
+
return await self._handle_config_commands(args)
|
47
|
+
|
48
|
+
# For other commands, we need a base URL
|
49
|
+
# Get effective configuration
|
50
|
+
config = self.config_manager.get_effective_config(args)
|
51
|
+
|
52
|
+
# Validate required configuration
|
53
|
+
if not config.base_url:
|
54
|
+
print(
|
55
|
+
format_error_message(
|
56
|
+
"Base URL is required. Use --base-url or configure a profile."
|
57
|
+
)
|
58
|
+
)
|
59
|
+
return 1
|
60
|
+
|
61
|
+
# Create and initialize client
|
62
|
+
async with FOClient(config) as client:
|
63
|
+
self.client = client
|
64
|
+
|
65
|
+
# Route to appropriate command handler
|
66
|
+
return await self._route_command(args)
|
67
|
+
|
68
|
+
except KeyboardInterrupt:
|
69
|
+
print("\n\nOperation cancelled by user")
|
70
|
+
return 130
|
71
|
+
except Exception as e:
|
72
|
+
self._handle_error(e, getattr(args, "verbose", False))
|
73
|
+
return 1
|
74
|
+
|
75
|
+
async def _run_demo(self) -> int:
|
76
|
+
"""Run the legacy demo mode."""
|
77
|
+
from .main import example_usage
|
78
|
+
|
79
|
+
print("Running demo mode...")
|
80
|
+
try:
|
81
|
+
await example_usage()
|
82
|
+
return 0
|
83
|
+
except Exception as e:
|
84
|
+
print(f"Demo error: {e}")
|
85
|
+
return 1
|
86
|
+
|
87
|
+
async def _route_command(self, args: argparse.Namespace) -> int:
|
88
|
+
"""Route command to appropriate handler.
|
89
|
+
|
90
|
+
Args:
|
91
|
+
args: Parsed command line arguments
|
92
|
+
|
93
|
+
Returns:
|
94
|
+
Exit code
|
95
|
+
"""
|
96
|
+
command_handlers = {
|
97
|
+
"test": self._handle_test_command,
|
98
|
+
"version": self._handle_version_command,
|
99
|
+
"metadata": self._handle_metadata_commands,
|
100
|
+
"entity": self._handle_entity_commands,
|
101
|
+
"action": self._handle_action_commands,
|
102
|
+
}
|
103
|
+
|
104
|
+
command = getattr(args, "command", None)
|
105
|
+
handler = command_handlers.get(command)
|
106
|
+
|
107
|
+
if handler:
|
108
|
+
return await handler(args)
|
109
|
+
else:
|
110
|
+
print(format_error_message(f"Unknown command: {command}"))
|
111
|
+
return 1
|
112
|
+
|
113
|
+
async def _handle_test_command(self, args: argparse.Namespace) -> int:
|
114
|
+
"""Handle test connectivity command."""
|
115
|
+
success = True
|
116
|
+
|
117
|
+
# Test OData connection
|
118
|
+
if not getattr(args, "metadata_only", False):
|
119
|
+
try:
|
120
|
+
if await self.client.test_connection():
|
121
|
+
print(format_success_message("OData API connection successful"))
|
122
|
+
else:
|
123
|
+
print(format_error_message("OData API connection failed"))
|
124
|
+
success = False
|
125
|
+
except Exception as e:
|
126
|
+
print(format_error_message(f"OData API connection error: {e}"))
|
127
|
+
success = False
|
128
|
+
|
129
|
+
# Test metadata connection
|
130
|
+
if not getattr(args, "odata_only", False):
|
131
|
+
try:
|
132
|
+
if await self.client.test_metadata_connection():
|
133
|
+
print(format_success_message("Metadata API connection successful"))
|
134
|
+
else:
|
135
|
+
print(format_error_message("Metadata API connection failed"))
|
136
|
+
success = False
|
137
|
+
except Exception as e:
|
138
|
+
print(format_error_message(f"Metadata API connection error: {e}"))
|
139
|
+
success = False
|
140
|
+
|
141
|
+
return 0 if success else 1
|
142
|
+
|
143
|
+
async def _handle_version_command(self, args: argparse.Namespace) -> int:
|
144
|
+
"""Handle version information command."""
|
145
|
+
try:
|
146
|
+
version_info = {}
|
147
|
+
|
148
|
+
# Get different version types based on args
|
149
|
+
if getattr(args, "application", False) or getattr(args, "all", False):
|
150
|
+
version_info["application"] = (
|
151
|
+
await self.client.get_application_version()
|
152
|
+
)
|
153
|
+
|
154
|
+
if getattr(args, "platform", False) or getattr(args, "all", False):
|
155
|
+
version_info["platform_build"] = (
|
156
|
+
await self.client.get_platform_build_version()
|
157
|
+
)
|
158
|
+
|
159
|
+
if getattr(args, "build", False) or getattr(args, "all", False):
|
160
|
+
version_info["application_build"] = (
|
161
|
+
await self.client.get_application_build_version()
|
162
|
+
)
|
163
|
+
|
164
|
+
# If no specific version requested, get application version
|
165
|
+
if not version_info:
|
166
|
+
version_info["application"] = (
|
167
|
+
await self.client.get_application_version()
|
168
|
+
)
|
169
|
+
|
170
|
+
output = self.output_formatter.format_output(version_info)
|
171
|
+
print(output)
|
172
|
+
return 0
|
173
|
+
|
174
|
+
except Exception as e:
|
175
|
+
print(format_error_message(f"Error retrieving version information: {e}"))
|
176
|
+
return 1
|
177
|
+
|
178
|
+
async def _handle_metadata_commands(self, args: argparse.Namespace) -> int:
|
179
|
+
"""Handle metadata operations."""
|
180
|
+
subcommand = getattr(args, "metadata_subcommand", None)
|
181
|
+
|
182
|
+
if subcommand == "sync":
|
183
|
+
return await self._handle_metadata_sync(args)
|
184
|
+
elif subcommand == "search":
|
185
|
+
return await self._handle_metadata_search(args)
|
186
|
+
elif subcommand == "info":
|
187
|
+
return await self._handle_metadata_info(args)
|
188
|
+
else:
|
189
|
+
print(format_error_message(f"Unknown metadata subcommand: {subcommand}"))
|
190
|
+
return 1
|
191
|
+
|
192
|
+
async def _handle_metadata_sync(self, args: argparse.Namespace) -> int:
|
193
|
+
"""Handle metadata sync command."""
|
194
|
+
try:
|
195
|
+
force_refresh = getattr(args, "force", False)
|
196
|
+
success = await self.client.download_metadata(force_refresh=force_refresh)
|
197
|
+
|
198
|
+
if success:
|
199
|
+
print(format_success_message("Metadata synchronized successfully"))
|
200
|
+
return 0
|
201
|
+
else:
|
202
|
+
print(format_error_message("Metadata synchronization failed"))
|
203
|
+
return 1
|
204
|
+
except Exception as e:
|
205
|
+
print(format_error_message(f"Error syncing metadata: {e}"))
|
206
|
+
return 1
|
207
|
+
|
208
|
+
async def _handle_metadata_search(self, args: argparse.Namespace) -> int:
|
209
|
+
"""Handle metadata search command."""
|
210
|
+
try:
|
211
|
+
pattern = getattr(args, "pattern", "")
|
212
|
+
search_type = getattr(args, "type", "entities")
|
213
|
+
limit = getattr(args, "limit", None)
|
214
|
+
|
215
|
+
results = []
|
216
|
+
|
217
|
+
if search_type in ["entities", "all"]:
|
218
|
+
entities = self.client.search_entities(pattern)
|
219
|
+
if limit:
|
220
|
+
entities = entities[:limit]
|
221
|
+
results.extend([{"type": "entity", "name": name} for name in entities])
|
222
|
+
|
223
|
+
if search_type in ["actions", "all"]:
|
224
|
+
actions = self.client.search_actions(pattern)
|
225
|
+
if limit:
|
226
|
+
actions = actions[:limit]
|
227
|
+
results.extend([{"type": "action", "name": name} for name in actions])
|
228
|
+
|
229
|
+
if not results:
|
230
|
+
print(f"No {search_type} found matching pattern: {pattern}")
|
231
|
+
return 0
|
232
|
+
|
233
|
+
# If table format and mixed types, show type column
|
234
|
+
if self.output_formatter.format_type == "table" and search_type == "all":
|
235
|
+
output = self.output_formatter.format_output(results, ["type", "name"])
|
236
|
+
else:
|
237
|
+
# For specific types or other formats, just show names
|
238
|
+
names = [r["name"] for r in results]
|
239
|
+
output = self.output_formatter.format_output(names)
|
240
|
+
|
241
|
+
print(output)
|
242
|
+
return 0
|
243
|
+
|
244
|
+
except Exception as e:
|
245
|
+
print(format_error_message(f"Error searching metadata: {e}"))
|
246
|
+
return 1
|
247
|
+
|
248
|
+
async def _handle_metadata_info(self, args: argparse.Namespace) -> int:
|
249
|
+
"""Handle metadata info command."""
|
250
|
+
try:
|
251
|
+
entity_name = getattr(args, "entity_name", "")
|
252
|
+
|
253
|
+
entity_info = await self.client.get_entity_info_with_labels(entity_name)
|
254
|
+
if not entity_info:
|
255
|
+
print(format_error_message(f"Entity not found: {entity_name}"))
|
256
|
+
return 1
|
257
|
+
|
258
|
+
# Build info dictionary
|
259
|
+
info = {
|
260
|
+
"name": entity_info.name,
|
261
|
+
"label": entity_info.label_text or entity_info.label_id,
|
262
|
+
"properties_count": len(entity_info.enhanced_properties),
|
263
|
+
}
|
264
|
+
|
265
|
+
# Add properties if requested
|
266
|
+
if getattr(args, "properties", False):
|
267
|
+
properties = []
|
268
|
+
for prop in entity_info.enhanced_properties:
|
269
|
+
prop_info = {
|
270
|
+
"name": prop.name,
|
271
|
+
"type": prop.type,
|
272
|
+
"label": prop.label_text or prop.label_id,
|
273
|
+
}
|
274
|
+
if getattr(args, "keys", False):
|
275
|
+
prop_info["is_key"] = prop.is_key
|
276
|
+
properties.append(prop_info)
|
277
|
+
info["properties"] = properties
|
278
|
+
|
279
|
+
output = self.output_formatter.format_output(info)
|
280
|
+
print(output)
|
281
|
+
return 0
|
282
|
+
|
283
|
+
except Exception as e:
|
284
|
+
print(format_error_message(f"Error getting entity info: {e}"))
|
285
|
+
return 1
|
286
|
+
|
287
|
+
async def _handle_entity_commands(self, args: argparse.Namespace) -> int:
|
288
|
+
"""Handle entity operations."""
|
289
|
+
subcommand = getattr(args, "entity_subcommand", None)
|
290
|
+
|
291
|
+
if subcommand == "get":
|
292
|
+
return await self._handle_entity_get(args)
|
293
|
+
elif subcommand == "create":
|
294
|
+
return await self._handle_entity_create(args)
|
295
|
+
elif subcommand == "update":
|
296
|
+
return await self._handle_entity_update(args)
|
297
|
+
elif subcommand == "delete":
|
298
|
+
return await self._handle_entity_delete(args)
|
299
|
+
else:
|
300
|
+
print(format_error_message(f"Unknown entity subcommand: {subcommand}"))
|
301
|
+
return 1
|
302
|
+
|
303
|
+
async def _handle_entity_get(self, args: argparse.Namespace) -> int:
|
304
|
+
"""Handle entity get command."""
|
305
|
+
try:
|
306
|
+
entity_name = getattr(args, "entity_name", "")
|
307
|
+
key = getattr(args, "key", None)
|
308
|
+
|
309
|
+
# Build query options
|
310
|
+
query_options = None
|
311
|
+
if any(
|
312
|
+
[
|
313
|
+
getattr(args, "select", None),
|
314
|
+
getattr(args, "filter", None),
|
315
|
+
getattr(args, "top", None),
|
316
|
+
getattr(args, "orderby", None),
|
317
|
+
]
|
318
|
+
):
|
319
|
+
query_options = QueryOptions(
|
320
|
+
select=(
|
321
|
+
getattr(args, "select", "").split(",")
|
322
|
+
if getattr(args, "select", "")
|
323
|
+
else None
|
324
|
+
),
|
325
|
+
filter=getattr(args, "filter", None),
|
326
|
+
top=getattr(args, "top", None),
|
327
|
+
orderby=(
|
328
|
+
getattr(args, "orderby", "").split(",")
|
329
|
+
if getattr(args, "orderby", "")
|
330
|
+
else None
|
331
|
+
),
|
332
|
+
)
|
333
|
+
|
334
|
+
# Execute query
|
335
|
+
if key:
|
336
|
+
result = await self.client.get_entity(entity_name, key, query_options)
|
337
|
+
else:
|
338
|
+
result = await self.client.get_entities(entity_name, query_options)
|
339
|
+
|
340
|
+
# Format and output
|
341
|
+
if isinstance(result, dict) and "value" in result:
|
342
|
+
# OData response format
|
343
|
+
output = self.output_formatter.format_output(result["value"])
|
344
|
+
else:
|
345
|
+
output = self.output_formatter.format_output(result)
|
346
|
+
|
347
|
+
print(output)
|
348
|
+
return 0
|
349
|
+
|
350
|
+
except Exception as e:
|
351
|
+
print(format_error_message(f"Error getting entity data: {e}"))
|
352
|
+
return 1
|
353
|
+
|
354
|
+
async def _handle_entity_create(self, args: argparse.Namespace) -> int:
|
355
|
+
"""Handle entity create command."""
|
356
|
+
try:
|
357
|
+
entity_name = getattr(args, "entity_name", "")
|
358
|
+
|
359
|
+
# Get data from args
|
360
|
+
data_json = getattr(args, "data", None)
|
361
|
+
data_file = getattr(args, "file", None)
|
362
|
+
|
363
|
+
if data_json:
|
364
|
+
data = json.loads(data_json)
|
365
|
+
elif data_file:
|
366
|
+
with open(data_file, "r") as f:
|
367
|
+
data = json.load(f)
|
368
|
+
else:
|
369
|
+
print(format_error_message("Either --data or --file must be provided"))
|
370
|
+
return 1
|
371
|
+
|
372
|
+
result = await self.client.create_entity(entity_name, data)
|
373
|
+
|
374
|
+
print(format_success_message(f"Entity created successfully"))
|
375
|
+
output = self.output_formatter.format_output(result)
|
376
|
+
print(output)
|
377
|
+
return 0
|
378
|
+
|
379
|
+
except Exception as e:
|
380
|
+
print(format_error_message(f"Error creating entity: {e}"))
|
381
|
+
return 1
|
382
|
+
|
383
|
+
async def _handle_entity_update(self, args: argparse.Namespace) -> int:
|
384
|
+
"""Handle entity update command."""
|
385
|
+
try:
|
386
|
+
entity_name = getattr(args, "entity_name", "")
|
387
|
+
key = getattr(args, "key", "")
|
388
|
+
|
389
|
+
# Get data from args
|
390
|
+
data_json = getattr(args, "data", None)
|
391
|
+
data_file = getattr(args, "file", None)
|
392
|
+
|
393
|
+
if data_json:
|
394
|
+
data = json.loads(data_json)
|
395
|
+
elif data_file:
|
396
|
+
with open(data_file, "r") as f:
|
397
|
+
data = json.load(f)
|
398
|
+
else:
|
399
|
+
print(format_error_message("Either --data or --file must be provided"))
|
400
|
+
return 1
|
401
|
+
|
402
|
+
result = await self.client.update_entity(entity_name, key, data)
|
403
|
+
|
404
|
+
print(format_success_message(f"Entity updated successfully"))
|
405
|
+
output = self.output_formatter.format_output(result)
|
406
|
+
print(output)
|
407
|
+
return 0
|
408
|
+
|
409
|
+
except Exception as e:
|
410
|
+
print(format_error_message(f"Error updating entity: {e}"))
|
411
|
+
return 1
|
412
|
+
|
413
|
+
async def _handle_entity_delete(self, args: argparse.Namespace) -> int:
|
414
|
+
"""Handle entity delete command."""
|
415
|
+
try:
|
416
|
+
entity_name = getattr(args, "entity_name", "")
|
417
|
+
key = getattr(args, "key", "")
|
418
|
+
|
419
|
+
# Check for confirmation if not provided
|
420
|
+
if not getattr(args, "confirm", False):
|
421
|
+
response = input(
|
422
|
+
f"Are you sure you want to delete {entity_name} with key '{key}'? (y/N): "
|
423
|
+
)
|
424
|
+
if response.lower() not in ["y", "yes"]:
|
425
|
+
print("Delete operation cancelled")
|
426
|
+
return 0
|
427
|
+
|
428
|
+
success = await self.client.delete_entity(entity_name, key)
|
429
|
+
|
430
|
+
if success:
|
431
|
+
print(format_success_message(f"Entity deleted successfully"))
|
432
|
+
return 0
|
433
|
+
else:
|
434
|
+
print(format_error_message("Delete operation failed"))
|
435
|
+
return 1
|
436
|
+
|
437
|
+
except Exception as e:
|
438
|
+
print(format_error_message(f"Error deleting entity: {e}"))
|
439
|
+
return 1
|
440
|
+
|
441
|
+
async def _handle_action_commands(self, args: argparse.Namespace) -> int:
|
442
|
+
"""Handle action operations."""
|
443
|
+
subcommand = getattr(args, "action_subcommand", None)
|
444
|
+
|
445
|
+
if subcommand == "list":
|
446
|
+
return await self._handle_action_list(args)
|
447
|
+
elif subcommand == "call":
|
448
|
+
return await self._handle_action_call(args)
|
449
|
+
else:
|
450
|
+
print(format_error_message(f"Unknown action subcommand: {subcommand}"))
|
451
|
+
return 1
|
452
|
+
|
453
|
+
async def _handle_action_list(self, args: argparse.Namespace) -> int:
|
454
|
+
"""Handle action list command."""
|
455
|
+
try:
|
456
|
+
pattern = getattr(args, "pattern", "")
|
457
|
+
entity = getattr(args, "entity", None)
|
458
|
+
|
459
|
+
actions = self.client.search_actions(pattern)
|
460
|
+
|
461
|
+
if entity:
|
462
|
+
# Filter actions for specific entity (this is a simplified approach)
|
463
|
+
actions = [
|
464
|
+
action for action in actions if entity.lower() in action.lower()
|
465
|
+
]
|
466
|
+
|
467
|
+
if not actions:
|
468
|
+
print(f"No actions found matching pattern: {pattern}")
|
469
|
+
return 0
|
470
|
+
|
471
|
+
output = self.output_formatter.format_output(actions)
|
472
|
+
print(output)
|
473
|
+
return 0
|
474
|
+
|
475
|
+
except Exception as e:
|
476
|
+
print(format_error_message(f"Error listing actions: {e}"))
|
477
|
+
return 1
|
478
|
+
|
479
|
+
async def _handle_action_call(self, args: argparse.Namespace) -> int:
|
480
|
+
"""Handle action call command."""
|
481
|
+
try:
|
482
|
+
action_name = getattr(args, "action_name", "")
|
483
|
+
entity_name = getattr(args, "entity", None)
|
484
|
+
parameters_json = getattr(args, "parameters", None)
|
485
|
+
|
486
|
+
parameters = {}
|
487
|
+
if parameters_json:
|
488
|
+
parameters = json.loads(parameters_json)
|
489
|
+
|
490
|
+
result = await self.client.call_action(action_name, parameters, entity_name)
|
491
|
+
|
492
|
+
print(
|
493
|
+
format_success_message(f"Action '{action_name}' executed successfully")
|
494
|
+
)
|
495
|
+
output = self.output_formatter.format_output(result)
|
496
|
+
print(output)
|
497
|
+
return 0
|
498
|
+
|
499
|
+
except Exception as e:
|
500
|
+
print(format_error_message(f"Error calling action: {e}"))
|
501
|
+
return 1
|
502
|
+
|
503
|
+
async def _handle_config_commands(self, args: argparse.Namespace) -> int:
|
504
|
+
"""Handle configuration management commands."""
|
505
|
+
subcommand = getattr(args, "config_subcommand", None)
|
506
|
+
|
507
|
+
if subcommand == "list":
|
508
|
+
return self._handle_config_list(args)
|
509
|
+
elif subcommand == "show":
|
510
|
+
return self._handle_config_show(args)
|
511
|
+
elif subcommand == "create":
|
512
|
+
return self._handle_config_create(args)
|
513
|
+
elif subcommand == "update":
|
514
|
+
return self._handle_config_update(args)
|
515
|
+
elif subcommand == "delete":
|
516
|
+
return self._handle_config_delete(args)
|
517
|
+
elif subcommand == "set-default":
|
518
|
+
return self._handle_config_set_default(args)
|
519
|
+
else:
|
520
|
+
print(format_error_message(f"Unknown config subcommand: {subcommand}"))
|
521
|
+
return 1
|
522
|
+
|
523
|
+
def _handle_config_list(self, args: argparse.Namespace) -> int:
|
524
|
+
"""Handle config list command."""
|
525
|
+
try:
|
526
|
+
profiles = self.config_manager.list_profiles()
|
527
|
+
default_profile = self.config_manager.get_default_profile()
|
528
|
+
|
529
|
+
if not profiles:
|
530
|
+
print("No configuration profiles found")
|
531
|
+
return 0
|
532
|
+
|
533
|
+
profile_list = []
|
534
|
+
for name, profile in profiles.items():
|
535
|
+
profile_info = {
|
536
|
+
"name": name,
|
537
|
+
"base_url": profile.base_url,
|
538
|
+
"auth_mode": profile.auth_mode,
|
539
|
+
"default": (
|
540
|
+
"✓" if default_profile and default_profile.name == name else ""
|
541
|
+
),
|
542
|
+
}
|
543
|
+
profile_list.append(profile_info)
|
544
|
+
|
545
|
+
output = self.output_formatter.format_output(
|
546
|
+
profile_list, ["name", "base_url", "auth_mode", "default"]
|
547
|
+
)
|
548
|
+
print(output)
|
549
|
+
return 0
|
550
|
+
|
551
|
+
except Exception as e:
|
552
|
+
print(format_error_message(f"Error listing profiles: {e}"))
|
553
|
+
return 1
|
554
|
+
|
555
|
+
def _handle_config_show(self, args: argparse.Namespace) -> int:
|
556
|
+
"""Handle config show command."""
|
557
|
+
try:
|
558
|
+
profile_name = getattr(args, "profile_name", "")
|
559
|
+
profile = self.config_manager.get_profile(profile_name)
|
560
|
+
|
561
|
+
if not profile:
|
562
|
+
print(format_error_message(f"Profile not found: {profile_name}"))
|
563
|
+
return 1
|
564
|
+
|
565
|
+
# Convert profile to dict for display
|
566
|
+
profile_dict = {
|
567
|
+
"name": profile.name,
|
568
|
+
"base_url": profile.base_url,
|
569
|
+
"auth_mode": profile.auth_mode,
|
570
|
+
"verify_ssl": profile.verify_ssl,
|
571
|
+
"output_format": profile.output_format,
|
572
|
+
"label_cache": profile.label_cache,
|
573
|
+
"label_expiry": profile.label_expiry,
|
574
|
+
"language": profile.language,
|
575
|
+
}
|
576
|
+
|
577
|
+
# Only show auth details if they exist
|
578
|
+
if profile.client_id:
|
579
|
+
profile_dict["client_id"] = profile.client_id
|
580
|
+
if profile.tenant_id:
|
581
|
+
profile_dict["tenant_id"] = profile.tenant_id
|
582
|
+
|
583
|
+
output = self.output_formatter.format_output(profile_dict)
|
584
|
+
print(output)
|
585
|
+
return 0
|
586
|
+
|
587
|
+
except Exception as e:
|
588
|
+
print(format_error_message(f"Error showing profile: {e}"))
|
589
|
+
return 1
|
590
|
+
|
591
|
+
def _handle_config_create(self, args: argparse.Namespace) -> int:
|
592
|
+
"""Handle config create command."""
|
593
|
+
try:
|
594
|
+
profile_name = getattr(args, "profile_name", "")
|
595
|
+
base_url = getattr(args, "base_url", "")
|
596
|
+
|
597
|
+
if not base_url:
|
598
|
+
print(
|
599
|
+
format_error_message(
|
600
|
+
"--base-url is required when creating a profile"
|
601
|
+
)
|
602
|
+
)
|
603
|
+
return 1
|
604
|
+
|
605
|
+
# Check if profile already exists
|
606
|
+
if self.config_manager.get_profile(profile_name):
|
607
|
+
print(format_error_message(f"Profile already exists: {profile_name}"))
|
608
|
+
return 1
|
609
|
+
|
610
|
+
# Create new profile
|
611
|
+
profile = Profile(
|
612
|
+
name=profile_name,
|
613
|
+
base_url=base_url,
|
614
|
+
auth_mode=getattr(args, "auth_mode", "default"),
|
615
|
+
client_id=getattr(args, "client_id", None),
|
616
|
+
client_secret=getattr(args, "client_secret", None),
|
617
|
+
tenant_id=getattr(args, "tenant_id", None),
|
618
|
+
verify_ssl=getattr(args, "verify_ssl", True),
|
619
|
+
output_format=getattr(args, "output_format", "table"),
|
620
|
+
use_label_cache=getattr(args, "label_cache", True),
|
621
|
+
label_cache_expiry_minutes=getattr(args, "label_expiry", 60),
|
622
|
+
use_cache_first=getattr(args, "use_cache_first", True),
|
623
|
+
timeout=getattr(args, "timeout", 60),
|
624
|
+
language=getattr(args, "language", "en-US"),
|
625
|
+
)
|
626
|
+
|
627
|
+
self.config_manager.save_profile(profile)
|
628
|
+
print(
|
629
|
+
format_success_message(f"Profile '{profile_name}' created successfully")
|
630
|
+
)
|
631
|
+
return 0
|
632
|
+
|
633
|
+
except Exception as e:
|
634
|
+
print(format_error_message(f"Error creating profile: {e}"))
|
635
|
+
return 1
|
636
|
+
|
637
|
+
def _handle_config_update(self, args: argparse.Namespace) -> int:
|
638
|
+
"""Handle config update command."""
|
639
|
+
# Similar to create but updates existing profile
|
640
|
+
print(format_error_message("Config update not yet implemented"))
|
641
|
+
return 1
|
642
|
+
|
643
|
+
def _handle_config_delete(self, args: argparse.Namespace) -> int:
|
644
|
+
"""Handle config delete command."""
|
645
|
+
try:
|
646
|
+
profile_name = getattr(args, "profile_name", "")
|
647
|
+
|
648
|
+
if not self.config_manager.get_profile(profile_name):
|
649
|
+
print(format_error_message(f"Profile not found: {profile_name}"))
|
650
|
+
return 1
|
651
|
+
|
652
|
+
success = self.config_manager.delete_profile(profile_name)
|
653
|
+
if success:
|
654
|
+
print(
|
655
|
+
format_success_message(
|
656
|
+
f"Profile '{profile_name}' deleted successfully"
|
657
|
+
)
|
658
|
+
)
|
659
|
+
return 0
|
660
|
+
else:
|
661
|
+
print(format_error_message(f"Failed to delete profile: {profile_name}"))
|
662
|
+
return 1
|
663
|
+
|
664
|
+
except Exception as e:
|
665
|
+
print(format_error_message(f"Error deleting profile: {e}"))
|
666
|
+
return 1
|
667
|
+
|
668
|
+
def _handle_config_set_default(self, args: argparse.Namespace) -> int:
|
669
|
+
"""Handle config set-default command."""
|
670
|
+
try:
|
671
|
+
profile_name = getattr(args, "profile_name", "")
|
672
|
+
|
673
|
+
success = self.config_manager.set_default_profile(profile_name)
|
674
|
+
if success:
|
675
|
+
print(format_success_message(f"Default profile set to: {profile_name}"))
|
676
|
+
return 0
|
677
|
+
else:
|
678
|
+
print(format_error_message(f"Profile not found: {profile_name}"))
|
679
|
+
return 1
|
680
|
+
|
681
|
+
except Exception as e:
|
682
|
+
print(format_error_message(f"Error setting default profile: {e}"))
|
683
|
+
return 1
|
684
|
+
|
685
|
+
def _handle_error(self, error: Exception, verbose: bool = False) -> None:
|
686
|
+
"""Handle and display errors consistently.
|
687
|
+
|
688
|
+
Args:
|
689
|
+
error: Exception that occurred
|
690
|
+
verbose: Whether to show detailed error information
|
691
|
+
"""
|
692
|
+
if isinstance(error, FOClientError):
|
693
|
+
print(format_error_message(str(error)))
|
694
|
+
else:
|
695
|
+
if verbose:
|
696
|
+
import traceback
|
697
|
+
|
698
|
+
traceback.print_exc()
|
699
|
+
else:
|
700
|
+
print(format_error_message(f"Unexpected error: {error}"))
|