PKµ´ÈH\ëÁ‚99msrestazure/version.py# -------------------------------------------------------------------------- # # Copyright (c) Microsoft Corporation. All rights reserved. # # The MIT License (MIT) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the ""Software""), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # # -------------------------------------------------------------------------- msrestazure_version = "0.4.1" PK@´dH¤Ê¡lx x "msrestazure/azure_configuration.py# -------------------------------------------------------------------------- # # Copyright (c) Microsoft Corporation. All rights reserved. # # The MIT License (MIT) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the ""Software""), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # # -------------------------------------------------------------------------- try: from configparser import NoOptionError except ImportError: from ConfigParser import NoOptionError from .version import msrestazure_version from msrest import Configuration from msrest.exceptions import raise_with_traceback class AzureConfiguration(Configuration): """Azure specific client configuration. :param str base_url: REST Service base URL. :param str filepath: Path to an existing config file (optional). """ def __init__(self, base_url, filepath=None): super(AzureConfiguration, self).__init__(base_url, filepath) self.long_running_operation_timeout = 30 self.add_user_agent("msrest_azure/{}".format(msrestazure_version)) def save(self, filepath): """Save current configuration to file. :param str filepath: Path to save file to. :raises: ValueError if supplied filepath cannot be written to. :rtype: None """ self._config.add_section("Azure") self._config.set("Azure", "long_running_operation_timeout", self.long_running_operation_timeout) return super(AzureConfiguration, self).save(filepath) def load(self, filepath): """Load configuration from existing file. :param str filepath: Path to existing config file. :raises: ValueError if supplied config file is invalid. :rtype: None """ try: self._config.read(filepath) self.long_running_operation_timeout = self._config.getint( "Azure", "long_running_operation_timeout") except (ValueError, EnvironmentError, NoOptionError): msg = "Supplied config file incompatible" raise_with_traceback(ValueError, msg) finally: self._clear_config() return super(AzureConfiguration, self).load(filepath) PKj¨¹H:"Í›êMêM%msrestazure/azure_active_directory.py# -------------------------------------------------------------------------- # # Copyright (c) Microsoft Corporation. All rights reserved. # # The MIT License (MIT) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the ""Software""), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # # -------------------------------------------------------------------------- import ast import re import time try: from urlparse import urlparse, parse_qs except ImportError: from urllib.parse import urlparse, parse_qs import keyring from oauthlib.oauth2 import BackendApplicationClient, LegacyApplicationClient from oauthlib.oauth2.rfc6749.errors import ( InvalidGrantError, MismatchingStateError, OAuth2Error, TokenExpiredError) from requests import RequestException import requests_oauthlib as oauth from msrest.authentication import OAuthTokenAuthentication from msrest.exceptions import TokenExpiredError as Expired from msrest.exceptions import ( AuthenticationError, raise_with_traceback) def _build_url(uri, paths, scheme): """Combine URL parts. :param str uri: The base URL. :param list paths: List of strings that make up the URL. :param str scheme: The URL scheme, 'http' or 'https'. :rtype: str :return: Combined, formatted URL. """ path = [str(p).strip('/') for p in paths] combined_path = '/'.join(path) parsed_url = urlparse(uri) replaced = parsed_url._replace(scheme=scheme) if combined_path: path = '/'.join([replaced.path, combined_path]) replaced = replaced._replace(path=path) new_url = replaced.geturl() new_url = new_url.replace('///', '//') return new_url def _http(uri, *extra): """Convert https URL to http. :param str uri: The base URL. :param str extra: Additional URL paths (optional). :rtype: str :return: An HTTP URL. """ return _build_url(uri, extra, 'http') def _https(uri, *extra): """Convert http URL to https. :param str uri: The base URL. :param str extra: Additional URL paths (optional). :rtype: str :return: An HTTPS URL. """ return _build_url(uri, extra, 'https') class AADMixin(OAuthTokenAuthentication): """Mixin for Authentication object. Provides some AAD functionality: - State validation - Token caching and retrieval - Default AAD configuration """ _auth_endpoint = "//login.microsoftonline.com" _china_auth_endpoint = "//login.chinacloudapi.cn" _token_uri = "/oauth2/token" _auth_uri = "/oauth2/authorize" _tenant = "common" _resource = 'https://management.core.windows.net/' _china_resource = "https://management.core.chinacloudapi.cn/" _keyring = "AzureAAD" _case = re.compile('([a-z0-9])([A-Z])') def _configure(self, **kwargs): """Configure authentication endpoint. Optional kwargs may include: - china (bool): Configure auth for China-based service, default is 'False'. - tenant (str): Alternative tenant, default is 'common'. - auth_uri (str): Alternative authentication endpoint. - token_uri (str): Alternative token retrieval endpoint. - resource (str): Alternative authentication resource, default is 'https://management.core.windows.net/'. - verify (bool): Verify secure connection, default is 'True'. - keyring (str): Name of local token cache, default is 'AzureAAD'. """ if kwargs.get('china'): auth_endpoint = self._china_auth_endpoint resource = self._china_resource else: auth_endpoint = self._auth_endpoint resource = self._resource tenant = kwargs.get('tenant', self._tenant) self.auth_uri = kwargs.get('auth_uri', _https( auth_endpoint, tenant, self._auth_uri)) self.token_uri = kwargs.get('token_uri', _https( auth_endpoint, tenant, self._token_uri)) self.verify = kwargs.get('verify', True) self.cred_store = kwargs.get('keyring', self._keyring) self.resource = kwargs.get('resource', resource) self.state = oauth.oauth2_session.generate_token() self.store_key = "{}_{}".format( self._auth_endpoint.strip('/'), self.store_key) def _check_state(self, response): """Validate state returned by AAD server. :param str response: URL returned by server redirect. :raises: ValueError if state does not match that of the request. :rtype: None """ query = parse_qs(urlparse(response).query) if self.state not in query.get('state', []): raise ValueError( "State received from server does not match that of request.") def _convert_token(self, token): """Convert token fields from camel case. :param dict token: An authentication token. :rtype: dict """ return {self._case.sub(r'\1_\2', k).lower(): v for k, v in token.items()} def _parse_token(self): # TODO: We could also check expires_on and use to update expires_in if self.token.get('expires_at'): countdown = float(self.token['expires_at']) - time.time() self.token['expires_in'] = countdown kwargs = {} if self.token.get('refresh_token'): kwargs['auto_refresh_url'] = self.token_uri kwargs['auto_refresh_kwargs'] = {'client_id': self.id, 'resource': self.resource} kwargs['token_updater'] = self._default_token_cache return kwargs def _default_token_cache(self, token): """Store token for future sessions. :param dict token: An authentication token. :rtype: None """ self.token = token keyring.set_password(self.cred_store, self.store_key, str(token)) def _retrieve_stored_token(self): """Retrieve stored token for new session. :raises: ValueError if no cached token found. :rtype: dict :return: Retrieved token. """ token = keyring.get_password(self.cred_store, self.store_key) if token is None: raise ValueError("No stored token found.") self.token = ast.literal_eval(str(token)) self.signed_session() def signed_session(self): """Create token-friendly Requests session, using auto-refresh. Used internally when a request is made. :rtype: requests_oauthlib.OAuth2Session :raises: TokenExpiredError if token can no longer be refreshed. """ kwargs = self._parse_token() try: new_session = oauth.OAuth2Session( self.id, token=self.token, **kwargs) return new_session except TokenExpiredError as err: raise_with_traceback(Expired, "", err) def clear_cached_token(self): """Clear any stored tokens. :raises: KeyError if failed to clear token. :rtype: None """ try: keyring.delete_password(self.cred_store, self.store_key) except keyring.errors.PasswordDeleteError: raise_with_traceback(KeyError, "Unable to clear token.") class AADRefreshMixin(object): """ Additional token refresh logic """ def refresh_session(self): """Return updated session if token has expired, attempts to refresh using newly acquired token. :rtype: requests.Session. """ if self.token.get('refresh_token'): try: return self.signed_session() except Expired: pass self.set_token() return self.signed_session() class AADTokenCredentials(AADMixin): """ Credentials objects for AAD token retrieved through external process e.g. Python ADAL lib. Optional kwargs may include: - china (bool): Configure auth for China-based service, default is 'False'. - tenant (str): Alternative tenant, default is 'common'. - auth_uri (str): Alternative authentication endpoint. - token_uri (str): Alternative token retrieval endpoint. - resource (str): Alternative authentication resource, default is 'https://management.core.windows.net/'. - verify (bool): Verify secure connection, default is 'True'. - keyring (str): Name of local token cache, default is 'AzureAAD'. - cached (bool): If true, will not attempt to collect a token, which can then be populated later from a cached token. :param dict token: Authentication token. :param str client_id: Client ID, if not set, Xplat Client ID will be used. """ def __init__(self, token, client_id=None, **kwargs): if not client_id: # Default to Xplat Client ID. client_id = '04b07795-8ddb-461a-bbee-02f9e1bf7b46' super(AADTokenCredentials, self).__init__(client_id, None) self._configure(**kwargs) if not kwargs.get('cached'): self.token = self._convert_token(token) self.signed_session() @classmethod def retrieve_session(cls, client_id=None): """Create AADTokenCredentials from a cached token if it has not yet expired. """ session = cls(None, None, client_id=client_id, cached=True) session._retrieve_stored_token() return session class UserPassCredentials(AADRefreshMixin, AADMixin): """Credentials object for Headless Authentication, i.e. AAD authentication via username and password. Headless Auth requires an AAD login (no a Live ID) that already has permission to access the resource e.g. an organization account, and that 2-factor auth be disabled. Optional kwargs may include: - china (bool): Configure auth for China-based service, default is 'False'. - tenant (str): Alternative tenant, default is 'common'. - auth_uri (str): Alternative authentication endpoint. - token_uri (str): Alternative token retrieval endpoint. - resource (str): Alternative authentication resource, default is 'https://management.core.windows.net/'. - verify (bool): Verify secure connection, default is 'True'. - keyring (str): Name of local token cache, default is 'AzureAAD'. - cached (bool): If true, will not attempt to collect a token, which can then be populated later from a cached token. :param str username: Account username. :param str password: Account password. :param str client_id: Client ID, if not set, Xplat Client ID will be used. :param str secret: Client secret, only if required by server. """ def __init__(self, username, password, client_id=None, secret=None, **kwargs): if not client_id: # Default to Xplat Client ID. client_id = '04b07795-8ddb-461a-bbee-02f9e1bf7b46' super(UserPassCredentials, self).__init__(client_id, None) self._configure(**kwargs) self.store_key += "_{}".format(username) self.username = username self.password = password self.secret = secret self.client = LegacyApplicationClient(client_id=self.id) if not kwargs.get('cached'): self.set_token() @classmethod def retrieve_session(cls, username, client_id=None): """Create ServicePrincipalCredentials from a cached token if it has not yet expired. """ session = cls(username, None, client_id=client_id, cached=True) session._retrieve_stored_token() return session def _setup_session(self): """Create token-friendly Requests session. :rtype: requests_oauthlib.OAuth2Session """ return oauth.OAuth2Session(client=self.client) def set_token(self): """Get token using Username/Password credentials. :raises: AuthenticationError if credentials invalid, or call fails. """ session = self._setup_session() optional = {} if self.secret: optional['client_secret'] = self.secret try: token = session.fetch_token(self.token_uri, client_id=self.id, username=self.username, password=self.password, resource=self.resource, verify=self.verify, **optional) except (RequestException, OAuth2Error, InvalidGrantError) as err: raise_with_traceback(AuthenticationError, "", err) self.token = token class ServicePrincipalCredentials(AADRefreshMixin, AADMixin): """Credentials object for Service Principle Authentication. Authenticates via a Client ID and Secret. Optional kwargs may include: - china (bool): Configure auth for China-based service, default is 'False'. - tenant (str): Alternative tenant, default is 'common'. - auth_uri (str): Alternative authentication endpoint. - token_uri (str): Alternative token retrieval endpoint. - resource (str): Alternative authentication resource, default is 'https://management.core.windows.net/'. - verify (bool): Verify secure connection, default is 'True'. - keyring (str): Name of local token cache, default is 'AzureAAD'. - cached (bool): If true, will not attempt to collect a token, which can then be populated later from a cached token. :param str client_id: Client ID. :param str secret: Client secret. """ def __init__(self, client_id, secret, **kwargs): super(ServicePrincipalCredentials, self).__init__(client_id, None) self._configure(**kwargs) self.secret = secret self.client = BackendApplicationClient(self.id) if not kwargs.get('cached'): self.set_token() @classmethod def retrieve_session(cls, client_id): """Create ServicePrincipalCredentials from a cached token if it has not yet expired. """ session = cls(client_id, None, cached=True) session._retrieve_stored_token() return session def _setup_session(self): """Create token-friendly Requests session. :rtype: requests_oauthlib.OAuth2Session """ return oauth.OAuth2Session(self.id, client=self.client) def set_token(self): """Get token using Client ID/Secret credentials. :raises: AuthenticationError if credentials invalid, or call fails. """ session = self._setup_session() try: token = session.fetch_token(self.token_uri, client_id=self.id, resource=self.resource, client_secret=self.secret, response_type="client_credentials", verify=self.verify) except (RequestException, OAuth2Error, InvalidGrantError) as err: raise_with_traceback(AuthenticationError, "", err) else: self.token = token class InteractiveCredentials(AADMixin): """Credentials object for Interactive/Web App Authentication. Requires that an AAD Client be configured with a redirect URL. Optional kwargs may include: - china (bool): Configure auth for China-based service, default is 'False'. - tenant (str): Alternative tenant, default is 'common'. - auth_uri (str): Alternative authentication endpoint. - token_uri (str): Alternative token retrieval endpoint. - resource (str): Alternative authentication resource, default is 'https://management.core.windows.net/'. - verify (bool): Verify secure connection, default is 'True'. - keyring (str): Name of local token cache, default is 'AzureAAD'. - cached (bool): If true, will not attempt to collect a token, which can then be populated later from a cached token. :param str client_id: Client ID. :param str redirect: Redirect URL. """ def __init__(self, client_id, redirect, **kwargs): super(InteractiveCredentials, self).__init__(client_id, None) self._configure(**kwargs) self.redirect = redirect if not kwargs.get('cached'): self.set_token() @classmethod def retrieve_session(cls, client_id, redirect): """Create InteractiveCredentials from a cached token if it has not yet expired. """ session = cls(client_id, redirect, cached=True) session._retrieve_stored_token() return session def _setup_session(self): """Create token-friendly Requests session. :rtype: requests_oauthlib.OAuth2Session """ return oauth.OAuth2Session(self.id, redirect_uri=self.redirect, state=self.state) def get_auth_url(self, msa=False, **additional_args): """Get URL to web portal for authentication. :param bool msa: Set to 'True' if authenticating with Live ID. Default is 'False'. :param additional_args: Set and additional kwargs for requrired AAD configuration: msdn.microsoft.com/en-us/library/azure/dn645542.aspx :rtype: Tuple :return: The URL for authentication (str), and state code that will be verified in the response (str). """ if msa: additional_args['domain_hint'] = 'live.com' session = self._setup_session() auth_url, state = session.authorization_url(self.auth_uri, resource=self.resource, **additional_args) return auth_url, state def set_token(self, response_url): """Get token using Authorization Code from redirected URL. :param str response_url: The full redirected URL from successful authentication. :raises: AuthenticationError if credentials invalid, or call fails. """ self._check_state(response_url) session = self._setup_session() if response_url.startswith(_http(self.redirect)): response_url = _https(response_url) elif not response_url.startswith(_https(self.redirect)): response_url = _https(self.redirect, response_url) try: token = session.fetch_token(self.token_uri, authorization_response=response_url, verify=self.verify) except (InvalidGrantError, OAuth2Error, MismatchingStateError, RequestException) as err: raise_with_traceback(AuthenticationError, "", err) else: self.token = token PK@´dH ÁZ}¾¾msrestazure/__init__.py# -------------------------------------------------------------------------- # # Copyright (c) Microsoft Corporation. All rights reserved. # # The MIT License (MIT) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the ""Software""), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # # -------------------------------------------------------------------------- from .azure_configuration import AzureConfiguration from .version import msrestazure_version __all__ = ["AzureConfiguration"] __version__ = msrestazure_version PKµ´ÈH¹Œ_oNoNmsrestazure/azure_operation.py# -------------------------------------------------------------------------- # # Copyright (c) Microsoft Corporation. All rights reserved. # # The MIT License (MIT) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the ""Software""), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # # -------------------------------------------------------------------------- import re import threading import time try: from urlparse import urlparse except ImportError: from urllib.parse import urlparse from msrest.exceptions import DeserializationError from msrestazure.azure_exceptions import CloudError FINISHED = frozenset(['succeeded', 'canceled', 'failed']) FAILED = frozenset(['canceled', 'failed']) SUCCEEDED = frozenset(['succeeded']) def finished(status): if hasattr(status, 'value'): status = status.value return str(status).lower() in FINISHED def failed(status): if hasattr(status, 'value'): status = status.value return str(status).lower() in FAILED def succeeded(status): if hasattr(status, 'value'): status = status.value return str(status).lower() in SUCCEEDED class BadStatus(Exception): pass class BadResponse(Exception): pass class OperationFailed(Exception): pass class OperationFinished(Exception): pass class SimpleResource: """An implementation of Python 3 SimpleNamespace. Used to deserialize resource objects from response bodies where no particular object type has been specified. """ def __init__(self, **kwargs): self.__dict__.update(kwargs) def __repr__(self): keys = sorted(self.__dict__) items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys) return "{}({})".format(type(self).__name__, ", ".join(items)) def __eq__(self, other): return self.__dict__ == other.__dict__ class LongRunningOperation(object): """LongRunningOperation Provides default logic for interpreting operation responses and status updates. """ _convert = re.compile('([a-z0-9])([A-Z])') def __init__(self, response, outputs): self.method = response.request.method self.status = "" self.resource = None self.get_outputs = outputs self.async_url = None self.location_url = None def _validate(self, url): """Validate header url :param str url: Polling URL extracted from response header. :returns: URL if valid. :raises: ValueError if URL has not scheme or host. """ if url is None: return None parsed = urlparse(url) if not parsed.scheme or not parsed.netloc: raise ValueError("Invalid URL header") return url def _check_status(self, response): """Check response status code is valid for a Put or Patch reqest. Must be 200, 202, or 204. :raises: BadStatus if invalid status. """ code = response.status_code if code in [200, 202] or (code == 201 and self.method == 'PUT') or \ (code == 204 and self.method in ['DELETE', 'POST']): return raise BadStatus( "Invalid return status for {!r} operation".format(self.method)) def _is_empty(self, response): """Check if response body contains meaningful content. :rtype: bool :raises: DeserializationError if response body contains invalid json data. """ if not response.content: return True try: body = response.json() return not body except ValueError: raise DeserializationError( "Error occurred in deserializing the response body.") def _deserialize(self, response): """Attempt to deserialize resource from response. :raises: OperationFailed if deserialized resource has status of failed or cancelled. :raises: OperationFinished if deserialised resource has status succeeded. """ self.resource = self.get_outputs(response) if self.method == 'PUT': resource_status = self._get_resource_status() if failed(resource_status): self.status = resource_status raise OperationFailed("Operation failed or cancelled") elif succeeded(resource_status): raise OperationFinished("Operation succeeded") elif resource_status: self.status = resource_status def _get_body_status(self, response): """Attempt to find status info in response body. :param requests.Response response: latest REST call response. :rtype: str :returns: Status if found, else 'None'. """ if self._is_empty(response): return None body = response.json() return body.get('status') def _get_resource_status(self): """ Attempt to get provisioning state from resource. :returns: Status if found, else 'None'. """ try: return self.resource.provisioning_state except AttributeError: pass try: return self.resource.properties.provisioning_state except AttributeError: return None def _object_from_response(self, response): """If deserialization fails, attempt to create object from response body regardless. Required functionality for Azure LRO's.... :param requests.Response response: latest REST call response. """ body = response.json() body = {self._convert.sub(r'\1_\2', k).lower(): v for k, v in body.items()} properties = body.get('properties') if properties: properties = {self._convert.sub(r'\1_\2', k).lower(): v for k, v in properties.items()} del body['properties'] body.update(properties) self.resource = SimpleResource(**body) else: self.resource = SimpleResource(**body) def _process_status(self, response): """Process response based on specific status code. :param requests.Response response: latest REST call response. """ process = getattr(self, '_status_' + str(response.status_code)) process(response) def _status_200(self, response): """Process response with status code 200. :param requests.Response response: latest REST call response. """ status = self._get_body_status(response) self.status = status if status else 'Succeeded' if not status: try: # Even if this fails, status '200' should be successful. self._deserialize(response) except CloudError: if self.method in ['PUT', 'PATCH']: self._object_from_response(response) def _status_201(self, response): """Process response with status code 201. :param requests.Response response: latest REST call response. :raises: BadResponse if response deserializes to CloudError. """ try: self._deserialize(response) if not self.status: self.status = 'Succeeded' except CloudError as err: raise BadResponse(str(err)) def _status_202(self, response): """Process response with status code 202. Just sets status to 'InProgress'. :param requests.Response response: latest REST call response. """ self.status = 'InProgress' def _status_204(self, response): """Process response with status code 204. Interpretted as successful with no payload. :param requests.Response response: latest REST call response. """ self.status = 'Succeeded' self.resource = None def is_done(self): """Check whether the operation can be considered complete. This is based on whether the data in the resource matches the current status. If there is not resource, we assume it's complete. :rtype: bool """ if (self.async_url or not self.resource) and \ self.method in ['PUT', 'PATCH']: return False resource_state = self._get_resource_status() try: return self.status.lower() == resource_state.lower() except AttributeError: return True def get_initial_status(self, response): """Process first response after initiating long running operation. :param requests.Response response: initial REST call response. """ self._check_status(response) if response.status_code in [200, 202, 204]: self._process_status(response) status = self._get_body_status(response) if status: self.status = status try: self._deserialize(response) except CloudError: pass def get_status_from_location(self, response): """Process the latest status update retrieved from a 'location' header. :param requests.Response response: latest REST call response. :raises: BadResponse if response has no body and not status 202. """ self._check_status(response) self._process_status(response) def get_status_from_resource(self, response): """Process the latest status update retrieved from the same URL as the previous request. :param requests.Response response: latest REST call response. :raises: BadResponse if status not 200 or 204. """ self._check_status(response) if self._is_empty(response) and self.method in ['PUT', 'PATCH']: raise BadResponse('The response from long running ' 'operation does not contain a body.') self._process_status(response) def get_status_from_async(self, response): """Process the latest status update retrieved from a 'azure-asyncoperation' header. :param requests.Response response: latest REST call response. :raises: BadResponse if response has no body, or body does not contain status. """ self._check_status(response) if self._is_empty(response): raise BadResponse('The response from long running operation ' 'does not contain a body.') self.status = self._get_body_status(response) if not self.status: raise BadResponse("No status found in body") if self.method in ["POST", "DELETE"]: try: self._deserialize(response) except CloudError: pass # Not all 'accept' statuses will deserialize. def get_retry(self, response, *args): """Retrieve the URL that will be polled for status. First looks for 'azure-asyncoperation' header, if not found or invalid, check for 'location' header. :param requests.Response response: latest REST call response. """ try: self.async_url = self._validate( response.headers.get('azure-asyncoperation')) # Return if we have a url, in case location header raises error. if self.async_url: return except ValueError: pass # We can ignore as location header may still be valid. self.location_url = self._validate(response.headers.get('location')) if not self.location_url and not self.async_url: code = response.status_code if code == 202 and self.method == 'POST': raise BadResponse( 'Location header is missing from long running operation.') class AzureOperationPoller(object): """Initiates long running operation and polls status in separate thread. :param callable send_cmd: The API request to initiate the operation. :param callable update_cmd: The API reuqest to check the status of the operation. :param callable output_cmd: The function to deserialize the resource of the operation. :param int timeout: Time in seconds to wait between status calls, default is 30. :param callable func: Callback function that takes at least one argument, a completed LongRunningOperation (optional). """ def __init__(self, send_cmd, output_cmd, update_cmd, timeout=30): self._timeout = timeout self._response = None self._operation = None self._exception = None self._callbacks = [] self._done = threading.Event() self._thread = threading.Thread( target=self._start, args=(send_cmd, update_cmd, output_cmd)) self._thread.start() def _start(self, send_cmd, update_cmd, output_cmd): """Start the long running operation. On completetion, runs any callbacks. :param callable send_cmd: The API request to initiate the operation. :param callable update_cmd: The API reuqest to check the status of the operation. :param callable output_cmd: The function to deserialize the resource of the operation. """ try: self._response = send_cmd() self._operation = LongRunningOperation(self._response, output_cmd) self._operation.get_initial_status(self._response) self._poll(update_cmd) except BadStatus: self._operation.status = 'Failed' self._exception = CloudError(self._response) except BadResponse as err: self._operation.status = 'Failed' self._exception = CloudError(self._response, str(err)) except OperationFailed: error = "Long running operation failed with status {!r}".format( str(self._operation.status)) self._exception = CloudError(self._response, error) except OperationFinished: pass except Exception as err: self._exception = err finally: self._done.set() callbacks, self._callbacks = self._callbacks, [] while callbacks: for call in callbacks: call(self._operation) callbacks, self._callbacks = self._callbacks, [] def _delay(self): """Check for a 'retry-after' header to set timeout, otherwise use configured timeout. """ if self._response is None: return if self._response.headers.get('retry-after'): time.sleep(int(self._response.headers['retry-after'])) else: time.sleep(self._timeout) def _polling_cookie(self): """Collect retry cookie - we only want to do this for the test server at this point, unless we implement a proper cookie policy. :returns: Dictionary containing a cookie header if required, otherwise an empty dictionary. """ parsed_url = urlparse(self._response.request.url) host = parsed_url.hostname.strip('.') if host == 'localhost': return {'cookie': self._response.headers.get('set-cookie', '')} return {} def _poll(self, update_cmd): """Poll status of operation so long as operation is incomplete and we have an endpoint to query. :param callable update_cmd: The function to call to retrieve the latest status of the long running operation. :raises: OperationFinished if operation status 'Succeeded'. :raises: OperationFailed if operation status 'Failed' or 'Cancelled'. :raises: BadStatus if response status invalid. :raises: BadResponse if response invalid. """ initial_url = self._response.request.url while not finished(self._operation.status): self._delay() url = self._response.request.url headers = self._polling_cookie() self._operation.get_retry(self._response, initial_url) if self._operation.async_url: self._response = update_cmd( self._operation.async_url, headers) self._operation.get_status_from_async( self._response) elif self._operation.location_url: self._response = update_cmd( self._operation.location_url, headers) self._operation.get_status_from_location( self._response) else: self._response = update_cmd(url, headers) self._operation.get_status_from_resource( self._response) if failed(self._operation.status): raise OperationFailed("Operation failed or cancelled") elif not self._operation.is_done(): self._response = update_cmd(initial_url) self._operation.get_status_from_resource( self._response) def result(self, timeout=None): """Return the result of the long running operation, or the result available after the specified timeout. :returns: The deserialized resource of the long running operation, if one is available. :raises CloudError: Server problem with the query. """ self.wait(timeout) return self._operation.resource def wait(self, timeout=None): """Wait on the long running operation for a specified length of time. :param int timeout: Perion of time to wait for the long running operation to complete. :raises CloudError: Server problem with the query. """ self._thread.join(timeout=timeout) try: raise self._exception except TypeError: pass def done(self): """Check status of the long running operation. :returns: 'True' if the process has completed, else 'False'. """ return not self._thread.isAlive() def add_done_callback(self, func): """Add callback function to be run once the long running operation has completed - regardless of the status of the operation. :param callable func: Callback function that takes at least one argument, a completed LongRunningOperation. :raises: ValueError if the long running operation has already completed. """ if self._done.is_set(): raise ValueError("Process is complete.") self._callbacks.append(func) def remove_done_callback(self, func): """Remove a callback from the long running operation. :param callable func: The function to be removed from the callbacks. :raises: ValueError if the long running operation has already completed. """ if self._done.is_set(): raise ValueError("Process is complete.") self._callbacks = [c for c in self._callbacks if c != func] PK@´dHý³¸]GGmsrestazure/azure_exceptions.py# -------------------------------------------------------------------------- # # Copyright (c) Microsoft Corporation. All rights reserved. # # The MIT License (MIT) # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the ""Software""), to # deal in the Software without restriction, including without limitation the # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or # sell copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # # -------------------------------------------------------------------------- from requests import RequestException from msrest.exceptions import ClientException from msrest.serialization import Deserializer from msrest.exceptions import DeserializationError class CloudErrorData(object): """Cloud Error Data object, deserialized from error data returned during a failed REST API call. """ _validation = {} _attribute_map = { 'error': {'key': 'code', 'type': 'str'}, 'message': {'key': 'message', 'type': 'str'}, 'data': {'key': 'values', 'type': '{str}'} } def __init__(self, *args, **kwargs): self.error = None self._message = None self.request_id = None self.error_time = None self.data = None super(CloudErrorData, self).__init__(*args) def __str__(self): """Cloud error message.""" return str(self._message) @classmethod def _get_subtype_map(cls): return {} @property def message(self): """Cloud error message.""" return self._message @message.setter def message(self, value): """Attempt to deconstruct error message to retrieve further error data. """ try: value = eval(value) except (SyntaxError, TypeError): pass try: value = value.get('value', value) msg_data = value.split('\n') self._message = msg_data[0] except AttributeError: self._message = value return try: self.request_id = msg_data[1].partition(':')[2] time_str = msg_data[2].partition(':') self.error_time = Deserializer.deserialize_iso( "".join(time_str[2:])) except (IndexError, DeserializationError): pass class CloudError(ClientException): """ClientError, exception raised for failed Azure REST call. Will attempt to deserialize response into meaningful error data. :param requests.Response response: Response object. :param str error: Optional error message. """ def __init__(self, response, error=None, *args): deserialize = Deserializer() self.error = None self.message = None self.response = response self.status_code = self.response.status_code self.request_id = None if error: self.message = error self.error = response else: try: data = response.json() except ValueError: data = response else: data = data.get('error', data) try: self.error = deserialize(CloudErrorData(), data) except DeserializationError: self.error = None try: self.message = self.error.message except AttributeError: self.message = None if not self.error or not self.message: try: content = response.json() except ValueError: server_message = "none" else: server_message = content.get('message', "none") try: response.raise_for_status() except RequestException as err: if not self.error: self.error = err if not self.message: if server_message == "none": server_message = str(err) msg = "Operation failed with status: {!r}. Details: {}" self.message = msg.format(response.reason, server_message) else: if not self.error: self.error = response if not self.message: msg = "Operation failed with status: {!r}. Details: {}" self.message = msg.format( response.status_code, server_message) super(CloudError, self).__init__(self.message, self.error, *args) def __str__(self): """Cloud error message""" return str(self.message) PKÏ´ÈH-ÊN=úú+msrestazure-0.4.1.dist-info/DESCRIPTION.rstAutoRest: Python Client Runtime - Azure Module =============================================== Installation ------------ To install: .. code-block:: bash $ pip install msrestazure Release History --------------- 2016-06-08 Version 0.4.1 ++++++++++++++++++++++++ **Bugfixes** - Fix for LRO PUT operation https://github.com/Azure/autorest/issues/1133 2016-05-25 Version 0.4.0 ++++++++++++++++++++++++ Update msrest dependency to 0.4.0 **Bugfixes** - Fix for several AAD issues https://github.com/Azure/autorest/issues/1055 - Fix for LRO PATCH bug and refactor https://github.com/Azure/autorest/issues/993 **Behaviour changes** - Needs Autorest > 0.17.0 Nightly 20160525 2016-04-26 Version 0.3.0 ++++++++++++++++++++++++ Update msrest dependency to 0.3.0 **Bugfixes** - Read only values are no longer in __init__ or sent to the server (https://github.com/Azure/autorest/pull/959) - Useless kwarg removed **Behaviour changes** - Needs Autorest > 0.16.0 Nightly 20160426 2016-03-31 Version 0.2.1 ++++++++++++++++++++++++ **Bugfixes** - Fix AzurePollerOperation if Swagger defines provisioning status as enum type (https://github.com/Azure/autorest/pull/892) 2016-03-25 Version 0.2.0 ++++++++++++++++++++++++ Update msrest dependency to 0.2.0 **Behaviour change** - async methods called with raw=True don't return anymore AzureOperationPoller but ClientRawResponse - Needs Autorest > 0.16.0 Nightly 20160324 2016-03-21 Version 0.1.2 ++++++++++++++++++++++++ Update msrest dependency to 0.1.3 **Bugfixes** - AzureOperationPoller.wait() failed to raise exception if query error (https://github.com/Azure/autorest/pull/856) 2016-03-04 Version 0.1.1 ++++++++++++++++++++++++ **Bugfixes** - Source package corrupted in Pypi (https://github.com/Azure/autorest/issues/799) 2016-03-04 Version 0.1.0 ++++++++++++++++++++++++ **Behaviour change** - Replaced _required attribute in CloudErrorData class with _validation dict. 2016-02-29 Version 0.0.2 ++++++++++++++++++++++++ **Bugfixes** - Fixed AAD bug to include connection verification in UserPassCredentials. (https://github.com/Azure/autorest/pull/725) - Source package corrupted in Pypi (https://github.com/Azure/autorest/issues/718) 2016-02-19 Version 0.0.1 ++++++++++++++++++++++++ - Initial release. PKÏ´ÈH€Äc¾¾)msrestazure-0.4.1.dist-info/metadata.json{"classifiers": ["Development Status :: 4 - Beta", "Programming Language :: Python", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "License :: OSI Approved :: MIT License", "Topic :: Software Development"], "extensions": {"python.details": {"contacts": [{"name": "Microsoft Corporation", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/xingwu1/autorest/tree/python/ClientRuntimes/Python/msrestazure"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "license": "MIT License", "metadata_version": "2.0", "name": "msrestazure", "run_requires": [{"requires": ["msrest (>=0.4.0)"]}], "summary": "AutoRest swagger generator Python client runtime. Azure-specific module.", "version": "0.4.1"}PKÏ´ÈHƒÒY÷ )msrestazure-0.4.1.dist-info/top_level.txtmsrestazure PKÏ´ÈHŒ''\\!msrestazure-0.4.1.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PKÏ´ÈHJˆrƒ7 7 $msrestazure-0.4.1.dist-info/METADATAMetadata-Version: 2.0 Name: msrestazure Version: 0.4.1 Summary: AutoRest swagger generator Python client runtime. Azure-specific module. Home-page: https://github.com/xingwu1/autorest/tree/python/ClientRuntimes/Python/msrestazure Author: Microsoft Corporation Author-email: UNKNOWN License: MIT License Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: License :: OSI Approved :: MIT License Classifier: Topic :: Software Development Requires-Dist: msrest (>=0.4.0) AutoRest: Python Client Runtime - Azure Module =============================================== Installation ------------ To install: .. code-block:: bash $ pip install msrestazure Release History --------------- 2016-06-08 Version 0.4.1 ++++++++++++++++++++++++ **Bugfixes** - Fix for LRO PUT operation https://github.com/Azure/autorest/issues/1133 2016-05-25 Version 0.4.0 ++++++++++++++++++++++++ Update msrest dependency to 0.4.0 **Bugfixes** - Fix for several AAD issues https://github.com/Azure/autorest/issues/1055 - Fix for LRO PATCH bug and refactor https://github.com/Azure/autorest/issues/993 **Behaviour changes** - Needs Autorest > 0.17.0 Nightly 20160525 2016-04-26 Version 0.3.0 ++++++++++++++++++++++++ Update msrest dependency to 0.3.0 **Bugfixes** - Read only values are no longer in __init__ or sent to the server (https://github.com/Azure/autorest/pull/959) - Useless kwarg removed **Behaviour changes** - Needs Autorest > 0.16.0 Nightly 20160426 2016-03-31 Version 0.2.1 ++++++++++++++++++++++++ **Bugfixes** - Fix AzurePollerOperation if Swagger defines provisioning status as enum type (https://github.com/Azure/autorest/pull/892) 2016-03-25 Version 0.2.0 ++++++++++++++++++++++++ Update msrest dependency to 0.2.0 **Behaviour change** - async methods called with raw=True don't return anymore AzureOperationPoller but ClientRawResponse - Needs Autorest > 0.16.0 Nightly 20160324 2016-03-21 Version 0.1.2 ++++++++++++++++++++++++ Update msrest dependency to 0.1.3 **Bugfixes** - AzureOperationPoller.wait() failed to raise exception if query error (https://github.com/Azure/autorest/pull/856) 2016-03-04 Version 0.1.1 ++++++++++++++++++++++++ **Bugfixes** - Source package corrupted in Pypi (https://github.com/Azure/autorest/issues/799) 2016-03-04 Version 0.1.0 ++++++++++++++++++++++++ **Behaviour change** - Replaced _required attribute in CloudErrorData class with _validation dict. 2016-02-29 Version 0.0.2 ++++++++++++++++++++++++ **Bugfixes** - Fixed AAD bug to include connection verification in UserPassCredentials. (https://github.com/Azure/autorest/pull/725) - Source package corrupted in Pypi (https://github.com/Azure/autorest/issues/718) 2016-02-19 Version 0.0.1 ++++++++++++++++++++++++ - Initial release. PKÏ´ÈHêEé"msrestazure-0.4.1.dist-info/RECORDmsrestazure/__init__.py,sha256=j1F_UoeZumCvs07iLwMFMaM_o2LVxwC3En-PoOcU7lU,1470 msrestazure/azure_active_directory.py,sha256=Juy3Wok1C6FUHVgu6XI2kjrhcyF7ZBjupzDkGctSepI,19946 msrestazure/azure_configuration.py,sha256=2MHT4xTwxKtAu9LRqHzVpkW_nYv_Xx0Koh-cxQ8ECBE,3192 msrestazure/azure_exceptions.py,sha256=bMG91C21a3Dnl63Ytz5GvNvAD69bP0DleAyi4bgzFys,5447 msrestazure/azure_operation.py,sha256=1koYjbqZ-nG2aCNCXoAg2bGYnAnDYOCB8Pj-DBJ8g4k,20079 msrestazure/version.py,sha256=VZftpOUvpJO-vSbM7bV6gX-5DhOsUCJynVhU5MTH7sw,1337 msrestazure-0.4.1.dist-info/DESCRIPTION.rst,sha256=vMkmAg-usPGcEj7fGV_KILfwdgjupz1AzY4eiVN74xA,2298 msrestazure-0.4.1.dist-info/METADATA,sha256=wNbTeGcBmcFf0GyXyjuT-oIX5SI6DSGxWg6dRGdHNs8,3127 msrestazure-0.4.1.dist-info/RECORD,, msrestazure-0.4.1.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 msrestazure-0.4.1.dist-info/metadata.json,sha256=nGgeKYu3B7PV1MsUodVsKi_0S_m58S89kuc242-F6es,958 msrestazure-0.4.1.dist-info/top_level.txt,sha256=VTVe3Z2VhGYqmnagxQN__92nbaitL4BSqIq_4GXvRhc,12 PKµ´ÈH\ëÁ‚99msrestazure/version.pyPK@´dH¤Ê¡lx x "mmsrestazure/azure_configuration.pyPKj¨¹H:"Í›êMêM%%msrestazure/azure_active_directory.pyPK@´dH ÁZ}¾¾R`msrestazure/__init__.pyPKµ´ÈH¹Œ_oNoNEfmsrestazure/azure_operation.pyPK@´dHý³¸]GGð´msrestazure/azure_exceptions.pyPKÏ´ÈH-ÊN=úú+tÊmsrestazure-0.4.1.dist-info/DESCRIPTION.rstPKÏ´ÈH€Äc¾¾)·Ómsrestazure-0.4.1.dist-info/metadata.jsonPKÏ´ÈHƒÒY÷ )¼×msrestazure-0.4.1.dist-info/top_level.txtPKÏ´ÈHŒ''\\!Ømsrestazure-0.4.1.dist-info/WHEELPKÏ´ÈHJˆrƒ7 7 $ªØmsrestazure-0.4.1.dist-info/METADATAPKÏ´ÈHêEé"#åmsrestazure-0.4.1.dist-info/RECORDPK ½wé