azext_iot/common/embedded_cli.py (69 lines of code) (raw):
# coding=utf-8
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------
import json
import shlex
from typing import Optional
from azure.cli.core import get_default_cli
from azure.cli.core.azclierror import CLIInternalError
from knack.log import get_logger
from io import StringIO
logger = get_logger(__name__)
class EmbeddedCLI(object):
"""
An embedded CLI wrapper for easily invoking commands.
...
Attributes
----------
output : str
The output of the last invoked cli command. If the last command failed or there were no runs,
will return ""
error_code : int
Error code of the last invoked cli command. If no runs, will be 0.
az_cli : AzCli
The cli that will be used for invoking commands. Should be the default CLI.
user_subscription : Optional[str]
The invoker's subscription.
capture_stderr : bool
Flag to determine whether we capture (don't print) output from invoked commands, but raise errors
when they occur.
"""
def __init__(self, cli_ctx=None, capture_stderr: bool = False):
super(EmbeddedCLI, self).__init__()
self.output = ""
self.error_code = 0
self.az_cli = get_default_cli()
self.user_subscription = cli_ctx.data.get('subscription_id') if cli_ctx else None
self.capture_stderr = capture_stderr
def invoke(
self, command: str, subscription: str = None, capture_stderr: Optional[bool] = None
):
"""
Run a given command.
Note that if capture_stderr is True, any error during invocation will be raised.
Parameters
----------
command : str
The command to invoke. Note that the command should omit the `az` from the command.
subscription : Optional[str]
Subscription for when it needs to be different from the self.user_subscription. Takes
precedence over self.user_subscription.
capture_stderr : Optional[bool]
Flag to determine whether we capture (don't print) output from invoked commands, but raise errors
when they occur. Takes precedence over self.capture_stderr.
"""
output_file = StringIO()
old_exception_handler = None
# if capture_stderr is defined, use that, otherwise default to self.capture_stderr
if (capture_stderr is None and self.capture_stderr) or capture_stderr:
# Stop exception from being logged
old_exception_handler = self.az_cli.exception_handler
self.az_cli.exception_handler = lambda _: None
command = self._ensure_json_output(command=command)
# prioritize subscription passed into invoke
if subscription:
command = self._ensure_subscription(
command=command, subscription=subscription
)
elif self.user_subscription:
command = self._ensure_subscription(
command=command, subscription=self.user_subscription
)
try:
self.error_code = (
self.az_cli.invoke(shlex.split(command), out_file=output_file) or 0
)
except SystemExit as se:
# Support caller error handling
self.error_code = se.code
self.output = output_file.getvalue()
logger.debug(
"Embedded CLI received error code: %s, output: '%s'",
self.error_code,
self.output,
)
if old_exception_handler:
self.az_cli.exception_handler = old_exception_handler
if self.get_error():
raise self.get_error()
output_file.close()
return self
def as_json(self):
"""
Try to parse the result of the last invoked cli command as a json.
If the json cannot be parsed, the last invoked cli command must have failed. This will raise a
CLIInternalError.
"""
try:
return json.loads(self.output)
except Exception:
raise CLIInternalError(
"Issue parsing received payload '{}' as json. Please try again or check resource status.".format(
self.output
)
)
def success(self) -> bool:
"""Return if last invoked cli command was a success."""
logger.debug("Operation error code: %s", self.error_code)
return self.error_code == 0
def get_error(self) -> Optional[Exception]:
"""Return error from last invoked cli command."""
return self.az_cli.result.error
def _ensure_json_output(self, command: str) -> str:
"""Force invoked cli command to return a json."""
return "{} -o json".format(command)
def _ensure_subscription(self, command: str, subscription: str) -> str:
"""Add subscription to invoked cli command."""
return "{} --subscription '{}'".format(command, subscription)