diff --git a/pulpcore/cli/common.py b/pulpcore/cli/common.py index 09bbfc2ab..c067623c8 100644 --- a/pulpcore/cli/common.py +++ b/pulpcore/cli/common.py @@ -8,7 +8,7 @@ import toml -from pulpcore.cli.openapi import OpenAPI +from pulpcore.cli.openapi import OpenAPI, OpenAPIError class PulpJSONEncoder(json.JSONEncoder): @@ -38,7 +38,10 @@ def output_result(self, result: Any) -> None: raise NotImplementedError(f"Format '{self.format}' not implemented.") def call(self, operation_id: str, background: bool = False, *args: Any, **kwargs: Any) -> Any: - result = self.api.call(operation_id, *args, **kwargs) + try: + result = self.api.call(operation_id, *args, **kwargs) + except OpenAPIError as e: + raise click.ClickException(str(e)) if "task" in result: task_href = result["task"] click.echo(f"Started task {task_href}", err=True) @@ -55,7 +58,9 @@ def wait_for_task(self, task_href: str, timeout: int = 120) -> Any: if task["state"] == "completed": return task if task["state"] == "failed": - raise click.ClickException("Task failed") + raise click.ClickException( + f"Task {task_href} failed: '{task['error']['description']}'" + ) if task["state"] == "canceled": raise click.ClickException("Task canceled") click.echo(".", nl=False, err=True) @@ -111,13 +116,16 @@ def main( ) -> None: if user and not password: password = click.prompt("password", hide_input=True) - api = OpenAPI( - base_url=base_url, - doc_path="/pulp/api/v3/docs/api.json", - username=user, - password=password, - validate_certs=verify_ssl, - refresh_cache=True, - debug_callback=lambda x: click.secho(x, err=True, bold=True) if verbose >= 1 else None, - ) + try: + api = OpenAPI( + base_url=base_url, + doc_path="/pulp/api/v3/docs/api.json", + username=user, + password=password, + validate_certs=verify_ssl, + refresh_cache=True, + debug_callback=lambda x: click.secho(x, err=True, bold=True) if verbose >= 1 else None, + ) + except OpenAPIError as e: + raise click.ClickException(str(e)) ctx.obj = PulpContext(api=api, format=format) diff --git a/pulpcore/cli/openapi.py b/pulpcore/cli/openapi.py index 11eeec6c6..00dc2c40e 100644 --- a/pulpcore/cli/openapi.py +++ b/pulpcore/cli/openapi.py @@ -13,6 +13,10 @@ import urllib3 +class OpenAPIError(Exception): + pass + + class OpenAPI: def __init__( self, @@ -39,9 +43,9 @@ def __init__( if username and password: self._session.auth = (username, password) elif username: - raise Exception("Password is required if username is set.") + raise OpenAPIError("Password is required if username is set.") elif password: - raise Exception("Username is required if password is set.") + raise OpenAPIError("Username is required if password is set.") self._session.headers.update(headers) self._session.verify = validate_certs @@ -76,7 +80,7 @@ def _parse_api(self, data: bytes) -> None: if self.api_spec.get("openapi", "").startswith("3."): self.openapi_version: int = 3 else: - raise NotImplementedError("Unknown schema version") + raise OpenAPIError("Unknown schema version") self.operations: Dict[str, Any] = { method_entry["operationId"]: (method, path) for path, path_entry in self.api_spec["paths"].items() @@ -85,7 +89,10 @@ def _parse_api(self, data: bytes) -> None: } def _download_api(self) -> bytes: - r: requests.Response = self._session.get(urljoin(self.base_url, self.doc_path)) + try: + r: requests.Response = self._session.get(urljoin(self.base_url, self.doc_path)) + except requests.exceptions.ConnectionError as e: + raise OpenAPIError(str(e)) r.raise_for_status() return r.content @@ -117,7 +124,7 @@ def extract_params( item["name"] for item in param_spec.values() if item.get("required", False) ] if any(remaining_required): - raise Exception( + raise OpenAPIError( "Required parameters [{0}] missing for {1}.".format( ", ".join(remaining_required), param_type ) @@ -171,7 +178,7 @@ def render_body( boundary=boundary ) else: - raise Exception("No suitable content type for file upload specified.") + raise OpenAPIError("No suitable content type for file upload specified.") elif body: if any((content_type.startswith("application/json") for content_type in content_types)): data = str.encode(json.dumps(body)) @@ -185,7 +192,7 @@ def render_body( data = str.encode(urlencode(body)) headers["Content-Type"] = "application/x-www-form-urlencoded" else: - raise Exception("No suitable content type for file upload specified.") + raise OpenAPIError("No suitable content type for request specified.") headers["Content-Length"] = str(len(data)) return data @@ -231,7 +238,7 @@ def call( ) if any(parameters): - raise Exception( + raise OpenAPIError( "Parameter [{names}] not available for {operation_id}.".format( names=", ".join(parameters.keys()), operation_id=operation_id )