Hello community,

here is the log from the commit of package python-msrestazure for 
openSUSE:Factory checked in at 2018-05-13 16:03:40
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-msrestazure (Old)
 and      /work/SRC/openSUSE:Factory/.python-msrestazure.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-msrestazure"

Sun May 13 16:03:40 2018 rev:5 rq:601546 version:0.4.28

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-msrestazure/python-msrestazure.changes    
2018-02-14 09:27:33.936299577 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-msrestazure.new/python-msrestazure.changes   
    2018-05-13 16:03:42.677295231 +0200
@@ -1,0 +2,12 @@
+Tue Apr 24 11:06:26 UTC 2018 - adrian.glaub...@suse.com
+
+- New upstream release
+  + Version 0.4.28
+  + For detailed information about changes see the
+    README.rst file provided with this package
+- Move LICENSE.txt from %doc to %license section
+- Refresh patches for new version
+  + m_drop-compatible-releases-operator.patch
+- Update Requires from setup.py
+
+-------------------------------------------------------------------

Old:
----
  msrestazure-0.4.20.tar.gz

New:
----
  msrestazure-0.4.28.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-msrestazure.spec ++++++
--- /var/tmp/diff_new_pack.WMNJqk/_old  2018-05-13 16:03:43.441267359 +0200
+++ /var/tmp/diff_new_pack.WMNJqk/_new  2018-05-13 16:03:43.445267213 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-msrestazure
-Version:        0.4.20
+Version:        0.4.28
 Release:        0
 Summary:        AutoRest swagger generator
 License:        MIT
@@ -32,10 +32,9 @@
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
 Requires:       python-adal < 1.0.0
-Requires:       python-adal >= 0.4.7
-Requires:       python-keyring >= 5.6
+Requires:       python-adal >= 0.5.0
 Requires:       python-msrest < 2.0.0
-Requires:       python-msrest >= 0.4.25
+Requires:       python-msrest >= 0.4.28
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
 BuildArch:      noarch
 
@@ -59,7 +58,8 @@
 
 %files  %{python_files}
 %defattr(-,root,root,-)
-%doc LICENSE.md README.rst
+%doc README.rst
+%license LICENSE.md
 %{python_sitelib}/*
 
 %changelog

++++++ m_drop-compatible-releases-operator.patch ++++++
--- /var/tmp/diff_new_pack.WMNJqk/_old  2018-05-13 16:03:43.509264878 +0200
+++ /var/tmp/diff_new_pack.WMNJqk/_new  2018-05-13 16:03:43.513264732 +0200
@@ -1,11 +1,11 @@
-diff -Nru msrestazure-0.4.20.orig/setup.py msrestazure-0.4.20/setup.py
---- msrestazure-0.4.20.orig/setup.py   2018-01-08 23:26:51.000000000 +0100
-+++ msrestazure-0.4.20/setup.py        2018-01-26 13:33:02.600469484 +0100
-@@ -51,6 +51,6 @@
+diff -Nru msrestazure-0.4.28.orig/setup.py msrestazure-0.4.28/setup.py
+--- msrestazure-0.4.28.orig/setup.py   2018-04-24 01:45:02.000000000 +0200
++++ msrestazure-0.4.28/setup.py        2018-04-24 13:00:24.806436987 +0200
+@@ -50,6 +50,6 @@
+         'Topic :: Software Development'],
      install_requires=[
-         "msrest>=0.4.25,<2.0.0",
-         "keyring>=5.6",
--        "adal~=0.4.7"
-+        "adal>=0.4.7"
+         "msrest>=0.4.28,<2.0.0",
+-        "adal~=0.5.0"
++        "adal>=0.5.0"
      ],
  )

++++++ msrestazure-0.4.20.tar.gz -> msrestazure-0.4.28.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msrestazure-0.4.20/PKG-INFO 
new/msrestazure-0.4.28/PKG-INFO
--- old/msrestazure-0.4.20/PKG-INFO     2018-01-08 23:27:27.000000000 +0100
+++ new/msrestazure-0.4.28/PKG-INFO     2018-04-24 01:45:43.000000000 +0200
@@ -1,12 +1,11 @@
 Metadata-Version: 1.1
 Name: msrestazure
-Version: 0.4.20
+Version: 0.4.28
 Summary: AutoRest swagger generator Python client runtime. Azure-specific 
module.
 Home-page: https://github.com/Azure/msrestazure-for-python
 Author: Microsoft Corporation
 Author-email: azpysdkh...@microsoft.com
 License: MIT License
-Description-Content-Type: UNKNOWN
 Description: AutoRest: Python Client Runtime - Azure Module
         ===============================================
         
@@ -29,6 +28,76 @@
         Release History
         ---------------
         
+        2018-04-23 Version 0.4.28
+        +++++++++++++++++++++++++
+        
+        **Disclaimer**
+        
+        Do to some stability issues with "keyring" dependency that highly 
change from one system to another,
+        this package is no longer a dependency of "msrestazure".
+        If you were using the secured token cache of 
`ServicePrincipalCredentials` and `UserPassCredentials`, 
+        the feature is still available, but you need to install manually 
"keyring". The functionnality will activate automatically.
+        
+        2018-04-18 Version 0.4.27
+        +++++++++++++++++++++++++
+        
+        **Features**
+        
+        - Implements new features of msrest 0.4.28 on session improvement. See 
msrest ChangeLog for details.
+        
+        Update msrest dependency to 0.4.28
+        
+        2018-04-17 Version 0.4.26
+        +++++++++++++++++++++++++
+        
+        **Bugfixes**
+        
+        - IMDS/MSI: Retry on more error codes (#87)
+        - IMDS/MSI: fix a boundary case on timeout (#86)
+        
+        2018-03-29 Version 0.4.25
+        +++++++++++++++++++++++++
+        
+        **Features**
+        
+        - MSIAuthentication now uses IMDS endpoint if available
+        - MSIAuthentication can be used in any environment that defines 
MSI_ENDPOINT env variable
+        
+        2018-03-26 Version 0.4.24
+        +++++++++++++++++++++++++
+        
+        **Bugfix**
+        
+        - Fix parse_resource_id() tool to be case-insensitive to keywords when 
matching #81
+        - Add missing baseclass init call for AdalAuthentication #82
+        
+        2018-03-19 Version 0.4.23
+        +++++++++++++++++++++++++
+        
+        **Bugfix**
+        
+        - Fix LRO result if POST uses AsyncOperation header (Autorest.Python 
3.0 only) #79 
+        
+        2018-02-27 Version 0.4.22
+        +++++++++++++++++++++++++
+        
+        **Bugfix**
+        
+        - Remove a possible infinite loop with MSIAuthentication #77
+        
+        **Disclaimer**
+        
+        From this version, MSIAuthentication will fail instantly if you try to 
get MSI token
+        from a VM where the extension is not installed, or not yet ready.
+        You need to do your own retry mechanism if you think the extension is 
provisioning and
+        the call might succeed later.
+        This behavior is consistent with other Azure SDK implementation of MSI 
scenarios.
+        
+        2018-01-26 Version 0.4.21
+        +++++++++++++++++++++++++
+        
+        - Update allowed ADAL dependency to 0.5.x
+        
         2018-01-08 Version 0.4.20
         +++++++++++++++++++++++++
         
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msrestazure-0.4.20/README.rst 
new/msrestazure-0.4.28/README.rst
--- old/msrestazure-0.4.20/README.rst   2018-01-08 23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/README.rst   2018-04-24 01:45:02.000000000 +0200
@@ -20,6 +20,76 @@
 Release History
 ---------------
 
+2018-04-23 Version 0.4.28
++++++++++++++++++++++++++
+
+**Disclaimer**
+
+Do to some stability issues with "keyring" dependency that highly change from 
one system to another,
+this package is no longer a dependency of "msrestazure".
+If you were using the secured token cache of `ServicePrincipalCredentials` and 
`UserPassCredentials`, 
+the feature is still available, but you need to install manually "keyring". 
The functionnality will activate automatically.
+
+2018-04-18 Version 0.4.27
++++++++++++++++++++++++++
+
+**Features**
+
+- Implements new features of msrest 0.4.28 on session improvement. See msrest 
ChangeLog for details.
+
+Update msrest dependency to 0.4.28
+
+2018-04-17 Version 0.4.26
++++++++++++++++++++++++++
+
+**Bugfixes**
+
+- IMDS/MSI: Retry on more error codes (#87)
+- IMDS/MSI: fix a boundary case on timeout (#86)
+
+2018-03-29 Version 0.4.25
++++++++++++++++++++++++++
+
+**Features**
+
+- MSIAuthentication now uses IMDS endpoint if available
+- MSIAuthentication can be used in any environment that defines MSI_ENDPOINT 
env variable
+
+2018-03-26 Version 0.4.24
++++++++++++++++++++++++++
+
+**Bugfix**
+
+- Fix parse_resource_id() tool to be case-insensitive to keywords when 
matching #81
+- Add missing baseclass init call for AdalAuthentication #82
+
+2018-03-19 Version 0.4.23
++++++++++++++++++++++++++
+
+**Bugfix**
+
+- Fix LRO result if POST uses AsyncOperation header (Autorest.Python 3.0 only) 
#79 
+
+2018-02-27 Version 0.4.22
++++++++++++++++++++++++++
+
+**Bugfix**
+
+- Remove a possible infinite loop with MSIAuthentication #77
+
+**Disclaimer**
+
+From this version, MSIAuthentication will fail instantly if you try to get MSI 
token
+from a VM where the extension is not installed, or not yet ready.
+You need to do your own retry mechanism if you think the extension is 
provisioning and
+the call might succeed later.
+This behavior is consistent with other Azure SDK implementation of MSI 
scenarios.
+
+2018-01-26 Version 0.4.21
++++++++++++++++++++++++++
+
+- Update allowed ADAL dependency to 0.5.x
+
 2018-01-08 Version 0.4.20
 +++++++++++++++++++++++++
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/msrestazure-0.4.20/msrestazure/azure_active_directory.py 
new/msrestazure-0.4.28/msrestazure/azure_active_directory.py
--- old/msrestazure-0.4.20/msrestazure/azure_active_directory.py        
2018-01-08 23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure/azure_active_directory.py        
2018-04-24 01:45:02.000000000 +0200
@@ -42,7 +42,7 @@
     MismatchingStateError,
     OAuth2Error,
     TokenExpiredError)
-from requests import RequestException, ConnectionError
+from requests import RequestException, ConnectionError, HTTPError
 import requests
 import requests_oauthlib as oauth
 
@@ -57,11 +57,12 @@
 from msrest.exceptions import AuthenticationError, raise_with_traceback
 
 from msrestazure.azure_cloud import AZURE_CHINA_CLOUD, AZURE_PUBLIC_CLOUD
+from msrestazure.azure_configuration import AzureConfiguration
 
 _LOGGER = logging.getLogger(__name__)
 
 if not keyring:
-    _LOGGER.warning("Cannot load keyring on your system: %s", 
KEYRING_EXCEPTION)
+    _LOGGER.warning("Cannot load 'keyring' on your system (either not 
installed, or not configured correctly): %s", KEYRING_EXCEPTION)
 
 def _build_url(uri, paths, scheme):
     """Combine URL parts.
@@ -110,6 +111,7 @@
 class AADMixin(OAuthTokenAuthentication):
     """Mixin for Authentication object.
     Provides some AAD functionality:
+
     - State validation
     - Token caching and retrieval
     - Default AAD configuration
@@ -124,6 +126,7 @@
         """Configure authentication endpoint.
 
         Optional kwargs may include:
+
             - cloud_environment (msrestazure.azure_cloud.Cloud): A targeted 
cloud environment
             - china (bool): Configure auth for China-based service,
               default is 'False'.
@@ -191,13 +194,6 @@
         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.
@@ -225,28 +221,23 @@
         self.token = ast.literal_eval(str(token))
         self.signed_session()
 
-    def signed_session(self):
+    def signed_session(self, session=None):
         """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)
+        If a session object is provided, configure it directly. Otherwise,
+        create a new session and return it.
 
+        :param session: The session to configure for authentication
+        :type session: requests.Session
+        """
+        self._parse_token()
+        return super(AADMixin, self).signed_session(session)
+            
     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)
@@ -254,32 +245,13 @@
             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:
+
     - cloud_environment (msrestazure.azure_cloud.Cloud): A targeted cloud 
environment
     - china (bool): Configure auth for China-based service,
       default is 'False'.
@@ -313,12 +285,12 @@
         """Create AADTokenCredentials from a cached token if it has not
         yet expired.
         """
-        session = cls(None, None, client_id=client_id, cached=True)
+        session = cls(None, client_id=client_id, cached=True)
         session._retrieve_stored_token()
         return session
 
 
-class UserPassCredentials(AADRefreshMixin, AADMixin):
+class UserPassCredentials(AADMixin):
     """Credentials object for Headless Authentication,
     i.e. AAD authentication via username and password.
 
@@ -327,6 +299,7 @@
     that 2-factor auth be disabled.
 
     Optional kwargs may include:
+
     - cloud_environment (msrestazure.azure_cloud.Cloud): A targeted cloud 
environment
     - china (bool): Configure auth for China-based service,
       default is 'False'.
@@ -392,7 +365,8 @@
             if self.secret:
                 optional['client_secret'] = self.secret
             try:
-                token = session.fetch_token(self.token_uri, client_id=self.id,
+                token = session.fetch_token(self.token_uri,
+                                            client_id=self.id,
                                             username=self.username,
                                             password=self.password,
                                             resource=self.resource,
@@ -404,13 +378,47 @@
                 raise_with_traceback(AuthenticationError, "", err)
 
             self.token = token
+            self._default_token_cache(self.token)
+
+    def refresh_session(self, session=None):
+        """Return updated session if token has expired, attempts to
+        refresh using newly acquired token.
+
+        If a session object is provided, configure it directly. Otherwise,
+        create a new session and return it.
+
+        :param session: The session to configure for authentication
+        :type session: requests.Session
+        :rtype: requests.Session.
+        """
+        with self._setup_session() as session:
+            optional = {}
+            if self.secret:
+                optional['client_secret'] = self.secret
+            try:
+                token = session.refresh_token(self.token_uri,
+                                              client_id=self.id,
+                                              username=self.username,
+                                              password=self.password,
+                                              resource=self.resource,
+                                              verify=self.verify,
+                                              proxies=self.proxies,
+                                              timeout=self.timeout,
+                                              **optional)
+            except (RequestException, OAuth2Error, InvalidGrantError) as err:
+                raise_with_traceback(AuthenticationError, "", err)
+
+            self.token = token
+            self._default_token_cache(self.token)
+        return self.signed_session(session)
 
 
-class ServicePrincipalCredentials(AADRefreshMixin, AADMixin):
+class ServicePrincipalCredentials(AADMixin):
     """Credentials object for Service Principle Authentication.
     Authenticates via a Client ID and Secret.
 
     Optional kwargs may include:
+
     - cloud_environment (msrestazure.azure_cloud.Cloud): A targeted cloud 
environment
     - china (bool): Configure auth for China-based service,
       default is 'False'.
@@ -462,7 +470,8 @@
         """
         with self._setup_session() as session:
             try:
-                token = session.fetch_token(self.token_uri, client_id=self.id,
+                token = session.fetch_token(self.token_uri,
+                                            client_id=self.id,
                                             resource=self.resource,
                                             client_secret=self.secret,
                                             response_type="client_credentials",
@@ -473,9 +482,29 @@
                 raise_with_traceback(AuthenticationError, "", err)
             else:
                 self.token = token
+                self._default_token_cache(self.token)
+
+    def refresh_session(self, session=None):
+        """Alias to signed_session().
+
+        SP flow does not contain refresh_token, so this method is just asking 
a new
+        token to AD.
+
+        If a session object is provided, configure it directly. Otherwise,
+        create a new session and return it.
+
+        :param session: The session to configure for authentication
+        :type session: requests.Session
+        :rtype: requests.Session.
+        """
+        self.set_token()
+        return self.signed_session(session)
+                
 
 # For backward compatibility of import, but I doubt someone uses that...
 class InteractiveCredentials(object):
+    """This class has been removed and using it will raise a 
NotImplementedError error.
+    """
     def __init__(self, *args, **kwargs):
         raise NotImplementedError("InteractiveCredentials was not functionning 
and was removed. Please use ADAL and device code instead.")
 
@@ -483,64 +512,68 @@
     """A wrapper to use ADAL for Python easily to authenticate on Azure.
 
     .. versionadded:: 0.4.5
-    """
 
-    def __init__(self, adal_method, *args, **kwargs):
-        """Take an ADAL `acquire_token` method and its parameters.
+    Take an ADAL `acquire_token` method and its parameters.
 
-        :Example:
+    :Example:
 
-        .. code:: python
+    .. code:: python
 
-            context = 
adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL')
-            RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph 
resource
-            token = context.acquire_token_with_client_credentials(
-                RESOURCE,
-                "http://PythonSDK";,
-                "Key-Configured-In-Portal")
+        context = 
adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL')
+        RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph resource
+        token = context.acquire_token_with_client_credentials(
+            RESOURCE,
+            "http://PythonSDK";,
+            "Key-Configured-In-Portal")
 
-        can be written here:
+    can be written here:
 
-        .. code:: python
+    .. code:: python
 
-            context = 
adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL')
-            RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph 
resource
-            credentials = AdalAuthentication(
-                context.acquire_token_with_client_credentials,
-                RESOURCE,
-                "http://PythonSDK";,
-                "Key-Configured-In-Portal")
+        context = 
adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL')
+        RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph resource
+        credentials = AdalAuthentication(
+            context.acquire_token_with_client_credentials,
+            RESOURCE,
+            "http://PythonSDK";,
+            "Key-Configured-In-Portal")
 
-        or using a lambda if you prefer:
+    or using a lambda if you prefer:
 
-        .. code:: python
+    .. code:: python
 
-            context = 
adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL')
-            RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph 
resource
-            credentials = AdalAuthentication(
-                lambda: context.acquire_token_with_client_credentials(
-                    RESOURCE,
-                    "http://PythonSDK";,
-                    "Key-Configured-In-Portal"
-                )
+        context = 
adal.AuthenticationContext('https://login.microsoftonline.com/ABCDEFGH-1234-1234-1234-ABCDEFGHIJKL')
+        RESOURCE = '00000002-0000-0000-c000-000000000000' #AAD graph resource
+        credentials = AdalAuthentication(
+            lambda: context.acquire_token_with_client_credentials(
+                RESOURCE,
+                "http://PythonSDK";,
+                "Key-Configured-In-Portal"
             )
+        )
 
-        :param adal_method: A lambda with no args, or `acquire_token` method 
with args using args/kwargs
-        :param args: Optional args for the method
-        :param kwargs: Optional kwargs for the method
-        """
+    :param callable adal_method: A lambda with no args, or `acquire_token` 
method with args using args/kwargs
+    :param args: Optional positional args for the method
+    :param kwargs: Optional kwargs for the method
+    """
+
+    def __init__(self, adal_method, *args, **kwargs):
+        super(AdalAuthentication, self).__init__()
         self._adal_method = adal_method
         self._args = args
         self._kwargs = kwargs
 
-    def signed_session(self):
-        """Get a signed session for requests.
+    def signed_session(self, session=None):
+        """Create requests session with any required auth headers applied.
 
-        Usually called by the Azure SDKs for you to authenticate queries.
+        If a session object is provided, configure it directly. Otherwise,
+        create a new session and return it.
 
+        :param session: The session to configure for authentication
+        :type session: requests.Session
         :rtype: requests.Session
         """
-        session = super(AdalAuthentication, self).signed_session()
+        session = super(AdalAuthentication, self).signed_session(session)
 
         try:
             raw_token = self._adal_method(*self._args, **self._kwargs)
@@ -558,17 +591,18 @@
         session.headers['Authorization'] = header
         return session
 
-
 def get_msi_token(resource, port=50342, msi_conf=None):
-    """Get MSI token from inside a VM/VMSS.
+    """Get MSI token if MSI_ENDPOINT is set.
+
+    IF MSI_ENDPOINT is not set, will try legacy access through 
'http://localhost:{}/oauth2/token'.format(port).
 
     If msi_conf is used, must be a dict of one key in ["client_id", 
"object_id", "msi_res_id"]
 
     :param str resource: The resource where the token would be use.
-    :param int port: The port is not the default 50342 is used.
-    :param dict[str, str] msi_conf: msi_conf if to request a token through a 
User Assigned Identity (if not specified, assume System Assigned)
+    :param int port: The port if not the default 50342 is used. Ignored if 
MSI_ENDPOINT is set.
+    :param dict[str,str] msi_conf: msi_conf if to request a token through a 
User Assigned Identity (if not specified, assume System Assigned)
     """
-    request_uri = 'http://localhost:{}/oauth2/token'.format(port)
+    request_uri = os.environ.get("MSI_ENDPOINT", 
'http://localhost:{}/oauth2/token'.format(port))
     payload = {
         'resource': resource
     }
@@ -577,28 +611,15 @@
             raise ValueError("{} are mutually 
exclusive".format(list(msi_conf.keys())))
         payload.update(msi_conf)
 
-    # retry as the token endpoint might not be available yet, one example is 
you use CLI in a
-    # custom script extension of VMSS, which might get provisioned before the 
MSI extensioon
-    while True:
-        err = None
-        try:
-            result = requests.post(request_uri, data=payload, 
headers={'Metadata': 'true'})
-            _LOGGER.debug("MSI: Retrieving a token from %s, with payload %s", 
request_uri, payload)
-            if result.status_code != 200:
-                err = result.text
-        except Exception as ex:  # pylint: disable=broad-except
-            err = str(ex)
-
-        if err:
-            # we might need some error code checking to avoid silly waiting. 
The bottom line is users can
-            # always press ctrl+c to stop it
-            _LOGGER.warning("MSI: Failed to retrieve a token from '%s' with an 
error of '%s'. This could be caused "
-                            "by the MSI extension not yet fullly provisioned. 
Will retry in 60 seconds...",
-                            request_uri, err)
-            time.sleep(60)
-        else:
-            _LOGGER.debug('MSI: token retrieved')
-            break
+    try:
+        result = requests.post(request_uri, data=payload, headers={'Metadata': 
'true'})
+        _LOGGER.debug("MSI: Retrieving a token from %s, with payload %s", 
request_uri, payload)
+        result.raise_for_status()
+    except Exception as ex:  # pylint: disable=broad-except
+        _LOGGER.warning("MSI: Failed to retrieve a token from '%s' with an 
error of '%s'. This could be caused "
+                        "by the MSI extension not yet fullly provisioned.",
+                        request_uri, ex)
+        raise 
     token_entry = result.json()
     return token_entry['token_type'], token_entry['access_token'], token_entry
 
@@ -606,8 +627,9 @@
     """Get a MSI token from inside a webapp or functions.
 
     Env variable will look like:
-    MSI_ENDPOINT = http://127.0.0.1:41741/MSI/token/
-    MSI_SECRET = 69418689F1E342DD946CB82994CDA3CB
+
+    - MSI_ENDPOINT = http://127.0.0.1:41741/MSI/token/
+    - MSI_SECRET = 69418689F1E342DD946CB82994CDA3CB
     """
     try:
         msi_endpoint = os.environ['MSI_ENDPOINT']
@@ -653,6 +675,7 @@
     """Credentials object for MSI authentication,.
 
     Optional kwargs may include:
+
     - client_id: Identifies, by Azure AD client id, a specific explicit 
identity to use when authenticating to Azure AD. Mutually exclusive with 
object_id and msi_res_id.
     - object_id: Identifies, by Azure AD object id, a specific explicit 
identity to use when authenticating to Azure AD. Mutually exclusive with 
client_id and msi_res_id.
     - msi_res_id: Identifies, by ARM resource id, a specific explicit identity 
to use when authenticating to Azure AD. Mutually exclusive with client_id and 
object_id.
@@ -660,27 +683,118 @@
     - resource (str): Alternative authentication resource, default
       is 'https://management.core.windows.net/'.
 
-    :param int port: MSI local port if VM/VMSS context (ignored otherwise)
+    .. versionadded:: 0.4.14
     """
 
     def __init__(self, port=50342, **kwargs):
         super(MSIAuthentication, self).__init__(None)
 
+        if port != 50342:
+            warnings.warn("The 'port' argument is no longer used, and will be 
removed in a future release", DeprecationWarning)
         self.port = port
+
         self.msi_conf = {k:v for k,v in kwargs.items() if k in ["client_id", 
"object_id", "msi_res_id"]}
 
         self.cloud_environment = kwargs.get('cloud_environment', 
AZURE_PUBLIC_CLOUD)
         self.resource = kwargs.get('resource', 
self.cloud_environment.endpoints.active_directory_resource_id)
 
-    def set_token(self):
         if _is_app_service():
             if self.msi_conf:
                 raise AuthenticationError("User Assigned Entity is not 
available on WebApp yet.")
+        elif "MSI_ENDPOINT" not in os.environ:
+            # Use IMDS if no MSI_ENDPOINT
+            self._vm_msi = _ImdsTokenProvider(self.resource, self.msi_conf)
+
+    def set_token(self):
+        if _is_app_service():
             self.scheme, _, self.token = get_msi_token_webapp(self.resource)
-        else:
+        elif "MSI_ENDPOINT" in os.environ:
             self.scheme, _, self.token = get_msi_token(self.resource, 
self.port, self.msi_conf)
+        else:
+            token_entry = self._vm_msi.get_token()
+            self.scheme, self.token = token_entry['token_type'], token_entry
+
+    def signed_session(self, session=None):
+        """Create requests session with any required auth headers applied.
+
+        If a session object is provided, configure it directly. Otherwise,
+        create a new session and return it.
 
-    def signed_session(self):
+        :param session: The session to configure for authentication
+        :type session: requests.Session
+        :rtype: requests.Session
+        """
         # Token cache is handled by the VM extension, call each time to avoid 
expiration
         self.set_token()
-        return super(MSIAuthentication, self).signed_session()
+        return super(MSIAuthentication, self).signed_session(session)
+
+
+class _ImdsTokenProvider(object):
+    """A help class handling token acquisitions through Azure IMDS plugin.
+    """
+
+    def __init__(self, resource, msi_conf=None):
+        self._user_agent = AzureConfiguration(None).user_agent
+        self.identity_type, self.identity_id = None, None
+        if msi_conf:
+            if len(msi_conf.keys()) > 1:
+                raise ValueError('"client_id", "object_id", "msi_res_id" are 
mutually exclusive')
+            elif len(msi_conf.keys()) == 1:
+                self.identity_type, self.identity_id = 
next(iter(msi_conf.items()))
+        # default to system assigned identity on an empty configuration object
+
+        self.cache = {}
+        self.resource = resource
+
+    def get_token(self):
+        import datetime
+        # let us hit the cache first
+        token_entry = self.cache.get(self.resource, None)
+        if token_entry:
+            expires_on = int(token_entry['expires_on'])
+            expires_on_datetime = datetime.datetime.fromtimestamp(expires_on)
+            expiration_margin = 5  # in minutes
+            if datetime.datetime.now() + 
datetime.timedelta(minutes=expiration_margin) <= expires_on_datetime:
+                _LOGGER.info("MSI: token is found in cache.")
+                return token_entry
+            _LOGGER.info("MSI: cache is found but expired within %s minutes, 
so getting a new one.", expiration_margin)
+            self.cache.pop(self.resource)
+
+        token_entry = self._retrieve_token_from_imds_with_retry()
+        self.cache[self.resource] = token_entry
+        return token_entry
+
+    def _retrieve_token_from_imds_with_retry(self):
+        import random
+        import json
+        # 169.254.169.254 is a well known ip address hosting the web service 
that provides the Azure IMDS metadata
+        request_uri = 'http://169.254.169.254/metadata/identity/oauth2/token'
+        payload = {
+            'resource': self.resource,
+            'api-version': '2018-02-01'
+        }
+        if self.identity_id:
+            payload[self.identity_type] = self.identity_id
+
+        retry, max_retry = 1, 20
+        # simplified version of 
https://en.wikipedia.org/wiki/Exponential_backoff
+        slots = [100 * ((2 << x) - 1) / 1000 for x in range(max_retry)]
+        while retry <= max_retry:
+            result = requests.get(request_uri, params=payload, 
headers={'Metadata': 'true', 'User-Agent':self._user_agent})
+            _LOGGER.debug("MSI: Retrieving a token from %s, with payload %s", 
request_uri, payload)
+            if result.status_code in [404, 429] or (499 < result.status_code < 
600):
+                wait = random.choice(slots[:retry])
+                _LOGGER.warning("MSI: Wait: %ss and retry: %s", wait, retry)
+                time.sleep(wait)
+                retry += 1
+            elif result.status_code != 200:
+                raise HTTPError(request=result.request, response=result.raw)
+            else:
+                break
+
+        if retry > max_retry:
+            raise TimeoutError('MSI: Failed to acquire tokens after {} 
times'.format(max_retry))
+
+        _LOGGER.debug('MSI: Token retrieved')
+        token_entry = json.loads(result.content.decode())
+        return token_entry
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/msrestazure-0.4.20/msrestazure/azure_configuration.py 
new/msrestazure-0.4.28/msrestazure/azure_configuration.py
--- old/msrestazure-0.4.20/msrestazure/azure_configuration.py   2018-01-08 
23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure/azure_configuration.py   2018-04-24 
01:45:02.000000000 +0200
@@ -72,7 +72,6 @@
 
         :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",
@@ -85,7 +84,6 @@
 
         :param str filepath: Path to existing config file.
         :raises: ValueError if supplied config file is invalid.
-        :rtype: None
         """
         try:
             self._config.read(filepath)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msrestazure-0.4.20/msrestazure/azure_operation.py 
new/msrestazure-0.4.28/msrestazure/azure_operation.py
--- old/msrestazure-0.4.20/msrestazure/azure_operation.py       2018-01-08 
23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure/azure_operation.py       2018-04-24 
01:45:02.000000000 +0200
@@ -309,6 +309,13 @@
         if not self.status:
             raise BadResponse("No status found in body")
 
+        # Status can contains information, see ARM spec:
+        # 
https://github.com/Azure/azure-resource-manager-rpc/blob/master/v1.0/Addendum.md#operation-resource-format
+        # "properties": {
+        # /\* The resource provider can choose the values here, but it should 
only be
+        #   returned on a successful operation (status being "Succeeded"). \*/
+        #},
+        # So try to parse it
         try:
             self.resource = self.get_outputs(response)
         except Exception:
@@ -328,6 +335,9 @@
     """Initiates long running operation and polls status in separate
     thread.
 
+    This class is used in old SDK and has been replaced. See "polling"
+    submodule now.
+
     :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.
@@ -492,7 +502,7 @@
 
         :param int timeout: Perion of time to wait for the long running
          operation to complete.
-        :raises CloudError: Server problem with the query.
+        :raises ~msrestazure.azure_exceptions.CloudError: Server problem with 
the query.
         """
         if self._thread is None:
             return
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/msrestazure-0.4.20/msrestazure/polling/arm_polling.py 
new/msrestazure-0.4.28/msrestazure/polling/arm_polling.py
--- old/msrestazure-0.4.20/msrestazure/polling/arm_polling.py   2018-01-08 
23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure/polling/arm_polling.py   2018-04-24 
01:45:02.000000000 +0200
@@ -269,6 +269,18 @@
         if not self.status:
             raise BadResponse("No status found in body")
 
+        # Status can contains information, see ARM spec:
+        # 
https://github.com/Azure/azure-resource-manager-rpc/blob/master/v1.0/Addendum.md#operation-resource-format
+        # "properties": {
+        # /\* The resource provider can choose the values here, but it should 
only be
+        #   returned on a successful operation (status being "Succeeded"). \*/
+        #},
+        # So try to parse it
+        try:
+            self.resource = self._deserialize(response)
+        except Exception:
+            self.resource = None            
+
     def set_async_url_if_present(self, response):
         async_url = get_header_url(response, 'azure-asyncoperation')
         if async_url:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msrestazure-0.4.20/msrestazure/tools.py 
new/msrestazure-0.4.28/msrestazure/tools.py
--- old/msrestazure-0.4.20/msrestazure/tools.py 2018-01-08 23:26:51.000000000 
+0100
+++ new/msrestazure-0.4.28/msrestazure/tools.py 2018-04-24 01:45:02.000000000 
+0200
@@ -32,15 +32,18 @@
 
 _LOGGER = logging.getLogger(__name__)
 _ARMID_RE = re.compile(
-    
'/subscriptions/(?P<subscription>[^/]*)(/resource[gG]roups/(?P<resource_group>[^/]*))?'
+    
'(?i)/subscriptions/(?P<subscription>[^/]*)(/resourceGroups/(?P<resource_group>[^/]*))?'
     
'/providers/(?P<namespace>[^/]*)/(?P<type>[^/]*)/(?P<name>[^/]*)(?P<children>.*)')
 
-_CHILDREN_RE = re.compile('(/providers/(?P<child_namespace>[^/]*))?/'
+_CHILDREN_RE = re.compile('(?i)(/providers/(?P<child_namespace>[^/]*))?/'
                           '(?P<child_type>[^/]*)/(?P<child_name>[^/]*)')
 
 def register_rp_hook(r, *args, **kwargs):
     """This is a requests hook to register RP automatically.
 
+    You should not use this command manually, this is added automatically
+    by the SDK.
+
     See requests documentation for details of the signature of this function.
     http://docs.python-requests.org/en/master/user/advanced/#event-hooks
     """
@@ -115,6 +118,7 @@
         - child_namespace_{level}: Namespace for the child resoure of that 
level
         - child_type_{level}:      Type of the child resource of that level
         - child_name_{level}:      Name of the child resource of that level
+        - last_child_num:          Level of the last child
         - resource_parent:         Computed parent in the following pattern: 
providers/{namespace}\
         /{parent}/{type}/{name}
         - resource_namespace:      Same as namespace. Note that this may be 
different than the \
@@ -122,7 +126,7 @@
         - resource_type:           Type of the target resource (not the parent)
         - resource_name:           Name of the target resource (not the parent)
 
-    :rtype: dict
+    :rtype: dict[str,str]
     """
     if not rid:
         return {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msrestazure-0.4.20/msrestazure/version.py 
new/msrestazure-0.4.28/msrestazure/version.py
--- old/msrestazure-0.4.20/msrestazure/version.py       2018-01-08 
23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure/version.py       2018-04-24 
01:45:02.000000000 +0200
@@ -24,4 +24,5 @@
 #
 # --------------------------------------------------------------------------
 
-msrestazure_version = "0.4.20"
+#: version of the package. Use msrestazure.__version__ instead.
+msrestazure_version = "0.4.28"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msrestazure-0.4.20/msrestazure.egg-info/PKG-INFO 
new/msrestazure-0.4.28/msrestazure.egg-info/PKG-INFO
--- old/msrestazure-0.4.20/msrestazure.egg-info/PKG-INFO        2018-01-08 
23:27:27.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure.egg-info/PKG-INFO        2018-04-24 
01:45:43.000000000 +0200
@@ -1,12 +1,11 @@
 Metadata-Version: 1.1
 Name: msrestazure
-Version: 0.4.20
+Version: 0.4.28
 Summary: AutoRest swagger generator Python client runtime. Azure-specific 
module.
 Home-page: https://github.com/Azure/msrestazure-for-python
 Author: Microsoft Corporation
 Author-email: azpysdkh...@microsoft.com
 License: MIT License
-Description-Content-Type: UNKNOWN
 Description: AutoRest: Python Client Runtime - Azure Module
         ===============================================
         
@@ -29,6 +28,76 @@
         Release History
         ---------------
         
+        2018-04-23 Version 0.4.28
+        +++++++++++++++++++++++++
+        
+        **Disclaimer**
+        
+        Do to some stability issues with "keyring" dependency that highly 
change from one system to another,
+        this package is no longer a dependency of "msrestazure".
+        If you were using the secured token cache of 
`ServicePrincipalCredentials` and `UserPassCredentials`, 
+        the feature is still available, but you need to install manually 
"keyring". The functionnality will activate automatically.
+        
+        2018-04-18 Version 0.4.27
+        +++++++++++++++++++++++++
+        
+        **Features**
+        
+        - Implements new features of msrest 0.4.28 on session improvement. See 
msrest ChangeLog for details.
+        
+        Update msrest dependency to 0.4.28
+        
+        2018-04-17 Version 0.4.26
+        +++++++++++++++++++++++++
+        
+        **Bugfixes**
+        
+        - IMDS/MSI: Retry on more error codes (#87)
+        - IMDS/MSI: fix a boundary case on timeout (#86)
+        
+        2018-03-29 Version 0.4.25
+        +++++++++++++++++++++++++
+        
+        **Features**
+        
+        - MSIAuthentication now uses IMDS endpoint if available
+        - MSIAuthentication can be used in any environment that defines 
MSI_ENDPOINT env variable
+        
+        2018-03-26 Version 0.4.24
+        +++++++++++++++++++++++++
+        
+        **Bugfix**
+        
+        - Fix parse_resource_id() tool to be case-insensitive to keywords when 
matching #81
+        - Add missing baseclass init call for AdalAuthentication #82
+        
+        2018-03-19 Version 0.4.23
+        +++++++++++++++++++++++++
+        
+        **Bugfix**
+        
+        - Fix LRO result if POST uses AsyncOperation header (Autorest.Python 
3.0 only) #79 
+        
+        2018-02-27 Version 0.4.22
+        +++++++++++++++++++++++++
+        
+        **Bugfix**
+        
+        - Remove a possible infinite loop with MSIAuthentication #77
+        
+        **Disclaimer**
+        
+        From this version, MSIAuthentication will fail instantly if you try to 
get MSI token
+        from a VM where the extension is not installed, or not yet ready.
+        You need to do your own retry mechanism if you think the extension is 
provisioning and
+        the call might succeed later.
+        This behavior is consistent with other Azure SDK implementation of MSI 
scenarios.
+        
+        2018-01-26 Version 0.4.21
+        +++++++++++++++++++++++++
+        
+        - Update allowed ADAL dependency to 0.5.x
+        
         2018-01-08 Version 0.4.20
         +++++++++++++++++++++++++
         
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msrestazure-0.4.20/msrestazure.egg-info/requires.txt 
new/msrestazure-0.4.28/msrestazure.egg-info/requires.txt
--- old/msrestazure-0.4.20/msrestazure.egg-info/requires.txt    2018-01-08 
23:27:27.000000000 +0100
+++ new/msrestazure-0.4.28/msrestazure.egg-info/requires.txt    2018-04-24 
01:45:43.000000000 +0200
@@ -1,3 +1,2 @@
-msrest<2.0.0,>=0.4.25
-keyring>=5.6
-adal~=0.4.7
+msrest<2.0.0,>=0.4.28
+adal~=0.5.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/msrestazure-0.4.20/setup.py 
new/msrestazure-0.4.28/setup.py
--- old/msrestazure-0.4.20/setup.py     2018-01-08 23:26:51.000000000 +0100
+++ new/msrestazure-0.4.28/setup.py     2018-04-24 01:45:02.000000000 +0200
@@ -28,7 +28,7 @@
 
 setup(
     name='msrestazure',
-    version='0.4.20',
+    version='0.4.28',
     author='Microsoft Corporation',
     author_email='azpysdkh...@microsoft.com',
     packages=find_packages(exclude=["tests", "tests.*"]),
@@ -49,8 +49,7 @@
         'License :: OSI Approved :: MIT License',
         'Topic :: Software Development'],
     install_requires=[
-        "msrest>=0.4.25,<2.0.0",
-        "keyring>=5.6",
-        "adal~=0.4.7"
+        "msrest>=0.4.28,<2.0.0",
+        "adal~=0.5.0"
     ],
 )


Reply via email to