Skip to content

Commit

Permalink
More useful exceptions for Key Vault errors (Azure#7086)
Browse files Browse the repository at this point in the history
  • Loading branch information
chlowell authored and yijxie committed Oct 9, 2019
1 parent 1fbfe63 commit 1a369c3
Show file tree
Hide file tree
Showing 9 changed files with 317 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
import functools
from typing import TYPE_CHECKING

from azure.core.exceptions import DecodeError, ResourceExistsError, ResourceNotFoundError
from azure.core.pipeline.policies import ContentDecodePolicy

if TYPE_CHECKING:
# pylint:disable=unused-import,ungrouped-imports
from typing import Type
from azure.core.exceptions import AzureError
from azure.core.pipeline.transport import HttpResponse


def get_exception_for_key_vault_error(cls, response):
# type: (Type[AzureError], HttpResponse) -> AzureError
try:
body = ContentDecodePolicy.deserialize_from_http_generics(response)
message = "({}) {}".format(body["error"]["code"], body["error"]["message"])
except (DecodeError, KeyError):
# Key Vault error response bodies have the expected shape and should be deserializable.
# If we somehow land here, we'll take HttpResponse's default message.
message = None

return cls(message=message, response=response)


_code_to_core_error = {404: ResourceNotFoundError, 409: ResourceExistsError}

# map status codes to callables returning appropriate azure-core errors
error_map = {
status_code: functools.partial(get_exception_for_key_vault_error, cls)
for status_code, cls in _code_to_core_error.items()
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
)
from ._polling_async import CreateCertificatePollerAsync
from .._shared import AsyncKeyVaultClientBase
from .._shared.exceptions import error_map


class CertificateClient(AsyncKeyVaultClientBase):
Expand Down Expand Up @@ -148,7 +149,9 @@ async def get_certificate_with_policy(
:param str name: The name of the certificate in the given vault.
:returns: An instance of Certificate
:rtype: ~azure.keyvault.certificates.models.Certificate
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the certificate doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
Example:
.. literalinclude:: ../tests/test_examples_certificates_async.py
Expand All @@ -162,6 +165,7 @@ async def get_certificate_with_policy(
vault_base_url=self.vault_url,
certificate_name=name,
certificate_version="",
error_map=error_map,
**kwargs
)
return Certificate._from_certificate_bundle(certificate_bundle=bundle)
Expand All @@ -182,7 +186,9 @@ async def get_certificate(
:param str version: The version of the certificate.
:returns: An instance of Certificate
:rtype: ~azure.keyvault.certificates.models.Certificate
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the certificate doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
Example:
.. literalinclude:: ../tests/test_examples_certificates_async.py
Expand All @@ -196,6 +202,7 @@ async def get_certificate(
vault_base_url=self.vault_url,
certificate_name=name,
certificate_version=version,
error_map=error_map,
**kwargs
)
return Certificate._from_certificate_bundle(certificate_bundle=bundle)
Expand All @@ -212,7 +219,9 @@ async def delete_certificate(self, name: str, **kwargs: "**Any") -> DeletedCerti
:param str name: The name of the certificate.
:returns: The deleted certificate
:rtype: ~azure.keyvault.certificates.models.DeletedCertificate
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the certificate doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
Example:
.. literalinclude:: ../tests/test_examples_certificates_async.py
Expand All @@ -225,6 +234,7 @@ async def delete_certificate(self, name: str, **kwargs: "**Any") -> DeletedCerti
bundle = await self._client.delete_certificate(
vault_base_url=self.vault_url,
certificate_name=name,
error_map=error_map,
**kwargs
)
return DeletedCertificate._from_deleted_certificate_bundle(deleted_certificate_bundle=bundle)
Expand All @@ -235,13 +245,15 @@ async def get_deleted_certificate(self, name: str, **kwargs: "**Any") -> Deleted
Retrieves the deleted certificate information plus its attributes,
such as retention interval, scheduled permanent deletion, and the
current deletion recovery level. This operaiton requires the certificates/
current deletion recovery level. This operation requires the certificates/
get permission.
:param str name: The name of the certificate.
:return: The deleted certificate
:rtype: ~azure.keyvault.certificates.models.DeletedCertificate
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the certificate doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
Example:
.. literalinclude:: ../tests/test_examples_certificates_async.py
Expand All @@ -254,6 +266,7 @@ async def get_deleted_certificate(self, name: str, **kwargs: "**Any") -> Deleted
bundle = await self._client.get_deleted_certificate(
vault_base_url=self.vault_url,
certificate_name=name,
error_map=error_map,
**kwargs
)
return DeletedCertificate._from_deleted_certificate_bundle(deleted_certificate_bundle=bundle)
Expand Down Expand Up @@ -467,7 +480,9 @@ async def backup_certificate(self, name: str, **kwargs: "**Any") -> bytes:
:param str name: The name of the certificate.
:return: the backup blob containing the backed up certificate.
:rtype: bytes
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the certificate doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
Example:
.. literalinclude:: ../tests/test_examples_certificates_async.py
Expand All @@ -480,6 +495,7 @@ async def backup_certificate(self, name: str, **kwargs: "**Any") -> bytes:
backup_result = await self._client.backup_certificate(
vault_base_url=self.vault_url,
certificate_name=name,
error_map=error_map,
**kwargs
)
return backup_result.value
Expand Down Expand Up @@ -710,12 +726,15 @@ async def get_certificate_operation(self, name: str, **kwargs: "**Any") -> Certi
:param str name: The name of the certificate.
:returns: The created CertificateOperation
:rtype: ~azure.keyvault.certificates.models.CertificateOperation
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the certificate doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
"""

bundle = await self._client.get_certificate_operation(
vault_base_url=self.vault_url,
certificate_name=name,
error_map=error_map,
**kwargs
)
return CertificateOperation._from_certificate_operation_bundle(certificate_operation_bundle=bundle)
Expand All @@ -731,11 +750,14 @@ async def delete_certificate_operation(self, name: str, **kwargs: "**Any") -> Ce
:param str name: The name of the certificate.
:return: The deleted CertificateOperation
:rtype: ~azure.keyvault.certificates.models.CertificateOperation
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the operation doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
"""
bundle = await self._client.delete_certificate_operation(
vault_base_url=self.vault_url,
certificate_name=name,
error_map=error_map,
**kwargs
)
return CertificateOperation._from_certificate_operation_bundle(certificate_operation_bundle=bundle)
Expand Down Expand Up @@ -775,7 +797,6 @@ async def get_pending_certificate_signing_request(
:rtype: str
:raises: :class:`~azure.core.exceptions.HttpResponseError`
"""
error_map = kwargs.pop("error_map", None)
vault_base_url = self.vault_url
# Construct URL
url = '/certificates/{certificate-name}/pending'
Expand Down Expand Up @@ -874,7 +895,9 @@ async def get_issuer(self, name: str, **kwargs: "**Any") -> Issuer:
:param str name: The name of the issuer.
:return: The specified certificate issuer.
:rtype: ~azure.keyvault.certificates.models.Issuer
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the issuer doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
Example:
.. literalinclude:: ../tests/test_examples_certificates_async.py
Expand All @@ -887,6 +910,7 @@ async def get_issuer(self, name: str, **kwargs: "**Any") -> Issuer:
issuer_bundle = await self._client.get_certificate_issuer(
vault_base_url=self.vault_url,
issuer_name=name,
error_map=error_map,
**kwargs
)
return Issuer._from_issuer_bundle(issuer_bundle=issuer_bundle)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from azure.core.tracing.decorator import distributed_trace

from ._shared import KeyVaultClientBase
from ._shared.exceptions import error_map
from .models import (
Certificate,
CertificateBase,
Expand Down Expand Up @@ -58,7 +59,7 @@ def create_certificate(
policy=None, # type: Optional[CertificatePolicy]
enabled=None, # type: Optional[bool]
tags=None, # type: Optional[Dict[str, str]]
**kwargs # type: **Any
**kwargs # type: Any
):
# type: (...) -> CertificateOperation
"""Creates a new certificate.
Expand Down Expand Up @@ -154,7 +155,9 @@ def get_certificate_with_policy(self, name, **kwargs):
:param str name: The name of the certificate in the given vault.
:returns: An instance of Certificate
:rtype: ~azure.keyvault.certificates.models.Certificate
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the certificate doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
Example:
.. literalinclude:: ../tests/test_examples_certificates.py
Expand All @@ -168,6 +171,7 @@ def get_certificate_with_policy(self, name, **kwargs):
vault_base_url=self.vault_url,
certificate_name=name,
certificate_version="",
error_map=error_map,
**kwargs
)
return Certificate._from_certificate_bundle(certificate_bundle=bundle)
Expand All @@ -184,7 +188,9 @@ def get_certificate(self, name, version, **kwargs):
:param str version: The version of the certificate.
:returns: An instance of Certificate
:rtype: ~azure.keyvault.certificates.models.Certificate
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the certificate doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
Example:
.. literalinclude:: ../tests/test_examples_certificates.py
Expand All @@ -198,6 +204,7 @@ def get_certificate(self, name, version, **kwargs):
vault_base_url=self.vault_url,
certificate_name=name,
certificate_version=version,
error_map=error_map,
**kwargs
)
return Certificate._from_certificate_bundle(certificate_bundle=bundle)
Expand All @@ -215,7 +222,9 @@ def delete_certificate(self, name, **kwargs):
:param str name: The name of the certificate.
:returns: The deleted certificate
:rtype: ~azure.keyvault.certificates.models.DeletedCertificate
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the certificate doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
Example:
.. literalinclude:: ../tests/test_examples_certificates.py
Expand All @@ -228,6 +237,7 @@ def delete_certificate(self, name, **kwargs):
bundle = self._client.delete_certificate(
vault_base_url=self.vault_url,
certificate_name=name,
error_map=error_map,
**kwargs
)
return DeletedCertificate._from_deleted_certificate_bundle(deleted_certificate_bundle=bundle)
Expand All @@ -239,13 +249,15 @@ def get_deleted_certificate(self, name, **kwargs):
Retrieves the deleted certificate information plus its attributes,
such as retention interval, scheduled permanent deletion, and the
current deletion recovery level. This operaiton requires the certificates/
current deletion recovery level. This operation requires the certificates/
get permission.
:param str name: The name of the certificate.
:return: The deleted certificate
:rtype: ~azure.keyvault.certificates.models.DeletedCertificate
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the certificate doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
Example:
.. literalinclude:: ../tests/test_examples_certificates.py
Expand All @@ -258,6 +270,7 @@ def get_deleted_certificate(self, name, **kwargs):
bundle = self._client.get_deleted_certificate(
vault_base_url=self.vault_url,
certificate_name=name,
error_map=error_map,
**kwargs
)
return DeletedCertificate._from_deleted_certificate_bundle(deleted_certificate_bundle=bundle)
Expand Down Expand Up @@ -475,7 +488,9 @@ def backup_certificate(self, name, **kwargs):
:param str name: The name of the certificate.
:return: the backup blob containing the backed up certificate.
:rtype: bytes
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the certificate doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
Example:
.. literalinclude:: ../tests/test_examples_certificates.py
Expand All @@ -488,6 +503,7 @@ def backup_certificate(self, name, **kwargs):
backup_result = self._client.backup_certificate(
vault_base_url=self.vault_url,
certificate_name=name,
error_map=error_map,
**kwargs
)
return backup_result.value
Expand Down Expand Up @@ -709,12 +725,15 @@ def get_certificate_operation(self, name, **kwargs):
:param str name: The name of the certificate.
:returns: The created CertificateOperation
:rtype: ~azure.keyvault.certificates.models.CertificateOperation
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the certificate doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
"""

bundle = self._client.get_certificate_operation(
vault_base_url=self.vault_url,
certificate_name=name,
error_map=error_map,
**kwargs
)
return CertificateOperation._from_certificate_operation_bundle(certificate_operation_bundle=bundle)
Expand Down Expand Up @@ -821,7 +840,6 @@ def get_pending_certificate_signing_request(
:rtype: str
:raises: :class:`~azure.core.exceptions.HttpResponseError`
"""
error_map = kwargs.pop("error_map", None)
vault_base_url = self.vault_url
# Construct URL
url = '/certificates/{certificate-name}/pending'
Expand Down Expand Up @@ -876,7 +894,9 @@ def get_issuer(self, name, **kwargs):
:param str name: The name of the issuer.
:return: The specified certificate issuer.
:rtype: ~azure.keyvault.certificates.models.Issuer
:raises: :class:`~azure.core.exceptions.HttpResponseError`
:raises:
:class:`~azure.core.exceptions.ResourceNotFoundError` if the issuer doesn't exist,
:class:`~azure.core.exceptions.HttpResponseError` for other errors
Example:
.. literalinclude:: ../tests/test_examples_certificates.py
Expand All @@ -889,6 +909,7 @@ def get_issuer(self, name, **kwargs):
issuer_bundle = self._client.get_certificate_issuer(
vault_base_url=self.vault_url,
issuer_name=name,
error_map=error_map,
**kwargs
)
return Issuer._from_issuer_bundle(issuer_bundle=issuer_bundle)
Expand Down
Loading

0 comments on commit 1a369c3

Please sign in to comment.