Skip to content

Commit 1b6675c

Browse files
committed
Fix the cli mixin
1 parent 5f77b01 commit 1b6675c

File tree

4 files changed

+93
-37
lines changed

4 files changed

+93
-37
lines changed

src/cli_mixin.py

Lines changed: 92 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import asyncio
44
import json
5-
import shlex
65

76
from fastmcp.utilities.logging import get_logger
87
from pydantic import BaseModel
@@ -102,60 +101,65 @@ async def execute_cli_command(
102101
flags: Dictionary of flag names and values
103102
output_format: Output format ('json', 'table', 'yaml')
104103
timeout: Command timeout in seconds
105-
auto_parse: If True, automatically parse JSON output
104+
auto_parse: Whether to automatically parse JSON output
106105
107106
Returns:
108-
CLIResult if auto_parse=False, parsed output if auto_parse=True
107+
CLIResult if auto_parse=False, parsed dict/str if auto_parse=True
109108
110109
Raises:
111110
CycloidCLIError: If command execution fails
112111
"""
113112
cmd_parts = self._build_command(subcommand, args, flags, output_format)
114-
command = " ".join(shlex.quote(part) for part in cmd_parts)
115-
116-
logger.debug("Executing Cycloid CLI command", extra={"command": command})
113+
command = " ".join(cmd_parts)
117114

118115
try:
119116
stdout, stderr, exit_code = await self._execute_command(cmd_parts, timeout)
120117

121118
result = CLIResult(
122119
success=exit_code == 0,
123-
stdout=stdout.decode("utf-8").strip(),
124-
stderr=stderr.decode("utf-8").strip(),
120+
stdout=stdout.decode() if stdout else "",
121+
stderr=stderr.decode() if stderr else "",
125122
exit_code=exit_code,
126123
command=command,
127124
)
128125

129126
if not result.success:
130127
logger.error(
131-
f"CLI command failed: {result.stderr}",
128+
f"CLI command failed with exit code {exit_code}",
132129
extra={
133130
"command": command,
134-
"exit_code": result.exit_code,
131+
"exit_code": exit_code,
135132
"stderr": result.stderr,
136133
},
137134
)
138135
raise CycloidCLIError(
139136
f"CLI command failed: {result.stderr}",
140137
command=command,
141-
exit_code=result.exit_code,
138+
exit_code=exit_code,
142139
stderr=result.stderr,
143140
)
144141

145-
# Auto-parse if requested
146-
if auto_parse and output_format == "json":
147-
try:
148-
return json.loads(result.stdout)
149-
except json.JSONDecodeError as e:
150-
logger.error(f"Failed to parse CLI JSON output: {str(e)}")
151-
raise CycloidCLIError(
152-
f"Failed to parse CLI JSON output: {str(e)}",
153-
command=result.command,
154-
exit_code=result.exit_code,
155-
stderr=result.stderr,
156-
)
157-
elif auto_parse:
158-
return result.stdout
142+
if auto_parse:
143+
if output_format == "json":
144+
try:
145+
return json.loads(result.stdout)
146+
except json.JSONDecodeError as e:
147+
logger.error(
148+
f"Failed to parse JSON output: {str(e)}",
149+
extra={
150+
"command": command,
151+
"stdout": result.stdout,
152+
"error": str(e),
153+
},
154+
)
155+
raise CycloidCLIError(
156+
f"Failed to parse JSON output: {str(e)}",
157+
command=command,
158+
exit_code=exit_code,
159+
stderr=f"JSON parse error: {str(e)}",
160+
)
161+
else:
162+
return result.stdout
159163

160164
return result
161165

@@ -189,7 +193,7 @@ async def execute_cli(
189193
flags: Optional[Dict[str, Union[str, bool]]] = None,
190194
output_format: str = "json",
191195
timeout: int = 30,
192-
) -> Union[Dict[str, Any], str]:
196+
) -> Union[Dict[str, Any], List[Dict[str, Any]], str]:
193197
"""
194198
Execute a Cycloid CLI command with automatic output parsing.
195199
@@ -201,20 +205,40 @@ async def execute_cli(
201205
timeout: Command timeout in seconds
202206
203207
Returns:
204-
Parsed output - JSON dict for 'json' format, string for others
208+
Parsed output - JSON dict/list for 'json' format, string for others
205209
206210
Raises:
207211
CycloidCLIError: If command execution fails or parsing fails
208212
"""
209213
result = await self.execute_cli_command(
210-
subcommand, args, flags, output_format, timeout, auto_parse=True
214+
subcommand, args, flags, output_format, timeout, auto_parse=False
211215
)
212-
# Type guard to ensure we return the expected types
213-
if isinstance(result, (dict, str)):
214-
return result
215-
else:
216-
# This shouldn't happen with auto_parse=True, but handle it gracefully
217-
return str(result)
216+
217+
# Parse the result if it's a CLIResult
218+
if isinstance(result, CLIResult):
219+
if output_format == "json":
220+
try:
221+
return self.parse_cli_output(result.stdout)
222+
except ValueError as e:
223+
logger.error(
224+
f"Failed to parse CLI JSON output: {str(e)}",
225+
extra={
226+
"command": result.command,
227+
"stdout": result.stdout,
228+
"error": str(e),
229+
},
230+
)
231+
raise CycloidCLIError(
232+
f"Failed to parse CLI JSON output: {str(e)}",
233+
command=result.command,
234+
exit_code=result.exit_code,
235+
stderr=result.stderr,
236+
)
237+
else:
238+
return result.stdout
239+
240+
# If auto_parse was already done, return as-is
241+
return result
218242

219243
@staticmethod
220244
def process_cli_response(
@@ -254,3 +278,37 @@ def process_cli_response(
254278
return default
255279
else:
256280
return default
281+
282+
@staticmethod
283+
def parse_cli_output(
284+
output: Union[str, Dict[str, Any], List[Dict[str, Any]]]
285+
) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
286+
"""
287+
Parse CLI output that may be in JSON or Python literal format.
288+
289+
Args:
290+
output: Raw CLI output (string, dict, or list)
291+
292+
Returns:
293+
Parsed output as dict or list
294+
295+
Raises:
296+
ValueError: If parsing fails completely
297+
"""
298+
# If already parsed, return as-is
299+
if isinstance(output, (dict, list)):
300+
return output
301+
302+
# Try to parse as JSON first
303+
try:
304+
return json.loads(output)
305+
except json.JSONDecodeError:
306+
# If JSON parsing fails, try to evaluate as Python literal
307+
try:
308+
import ast
309+
return ast.literal_eval(output)
310+
except (ValueError, SyntaxError):
311+
# If both fail, raise an error
312+
raise ValueError(
313+
f"Failed to parse CLI output as JSON or Python literal: {output[:100]}..."
314+
)

src/components/catalogs/catalogs_handler.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@ async def get_catalog_repositories(self) -> JSONList:
3333
"catalog-repository", ["list"], output_format="json"
3434
)
3535

36-
return self.cli.process_cli_response(
37-
repositories_data, list_key="service_catalog_repositories"
38-
)
36+
return self.cli.process_cli_response(repositories_data, list_key=None)
3937

4038
def format_table_output(self, repositories: JSONList, filter_text: str) -> str:
4139
"""Format repositories as table output."""

0 commit comments

Comments
 (0)