Repository: F5Networks/f5-icontrol-rest-python

Issue:
Update the User-Agent to include library and version
Add the library and version to the user agent header that is sent to the BIG-IP so that it can be logged to track usage and help debug problems.  This needs to be done in a way that allows users of this library to add additional strings to the end of the header.

The user agent string should be `f5-icontrol-rest-python/<version>`

Comments:

Patch:
diff --git a/icontrol/session.py b/icontrol/session.py
index 6abdb0a..376541f 100644
--- a/icontrol/session.py
+++ b/icontrol/session.py
@@ -58,7 +58,7 @@ against the BigIP REST Server, by pre- and post- processing the above methods.
 """
 
 from distutils.version import StrictVersion
-import functools
+from icontrol import __version__ as version
 from icontrol.authtoken import iControlRESTTokenAuth
 from icontrol.exceptions import iControlUnexpectedHTTPError
 from icontrol.exceptions import InvalidBigIP_ICRURI
@@ -67,6 +67,8 @@ from icontrol.exceptions import InvalidPrefixCollection
 from icontrol.exceptions import InvalidScheme
 from icontrol.exceptions import InvalidSuffixCollection
 from icontrol.exceptions import InvalidURIComponentPart
+
+import functools
 import logging
 import requests
 
@@ -266,9 +268,20 @@ class iControlRESTSession(object):
         All transactions are Trust On First Use (TOFU) to the BigIP device,
         since no PKI exists for this purpose in general, hence the
         "disable_warnings" statement.
+
+        :param str username: The user to connect with.
+        :param str password: The password of the user.
+        :param int timeout: The timeout, in seconds, to wait before closing
+                            the session.
+        :param bool token: True or False, specifying whether to use token
+                           authentication or not.
+        :param str user_agent: A string to append to the user agent header
+                              that is sent during a session.
         """
         timeout = kwargs.pop('timeout', 30)
         token_auth = kwargs.pop('token', None)
+        user_agent = kwargs.pop('user_agent', None)
+
         if kwargs:
             raise TypeError('Unexpected **kwargs: %r' % kwargs)
         requests_version = requests.__version__
@@ -295,6 +308,11 @@ class iControlRESTSession(object):
         self.session.verify = False  # XXXmake TOFU
         self.session.headers.update({'Content-Type': 'application/json'})
 
+        # Add a user agent for this library and any specified UA
+        self.append_user_agent('f5-icontrol-rest-python/' + version)
+        if user_agent:
+            self.append_user_agent(user_agent)
+
     @decorate_HTTP_verb_method
     def delete(self, uri, **kwargs):
         """Sends a HTTP DELETE command to the BIGIP REST Server.
@@ -409,8 +427,21 @@ class iControlRESTSession(object):
         :type json: dict
         :param name: The object name that will be appended to the uri
         :type name: str
-        :arg partition: The partition name that will be appened to the uri
+        :arg partition: The partition name that will be appended to the uri
         :type partition: str
         :param **kwargs: The :meth:`reqeusts.Session.put` optional params
         """
         return self.session.put(uri, data=data, **kwargs)
+
+    def append_user_agent(self, user_agent):
+        """Append text to the User-Agent header for the request.
+
+        Use this method to update the User-Agent header by appending the
+        given string to the session's User-Agent header separated by a space.
+
+        :param user_agent: A string to append to the User-Agent header
+        :type user_agent: str
+        """
+        old_ua = self.session.headers.get('User-Agent', '')
+        ua = old_ua + ' ' + user_agent
+        self.session.headers['User-Agent'] = ua.strip()


Test patch:
diff --git a/icontrol/test/unit/test_session.py b/icontrol/test/unit/test_session.py
index e1aa136..240505c 100644
--- a/icontrol/test/unit/test_session.py
+++ b/icontrol/test/unit/test_session.py
@@ -15,8 +15,11 @@
 import mock
 import pytest
 
+from icontrol import __version__ as VERSION
 from icontrol import session
 
+UA = 'f5-icontrol-rest-python/%s' % VERSION
+
 
 @pytest.fixture()
 def iCRS():
@@ -353,3 +356,32 @@ def test___init__with_2_9_1_requests_pkg():
         mock_requests.__version__ = '2.9.1'
         session.iControlRESTSession('test_name', 'test_pw')
         assert mock_requests.packages.urllib3.disable_warnings.called is False
+
+
+def test___init__user_agent():
+    icrs = session.iControlRESTSession('admin', 'admin')
+    assert UA in icrs.session.headers['User-Agent']
+
+
+def test__append_user_agent():
+    icrs = session.iControlRESTSession('admin', 'admin')
+    icrs.append_user_agent('test-user-agent/1.1.1')
+    assert icrs.session.headers['User-Agent'].endswith('test-user-agent/1.1.1')
+    assert UA in icrs.session.headers['User-Agent']
+
+
+def test_append_user_agent_empty_start():
+    icrs = session.iControlRESTSession('admin', 'admin')
+    icrs.session.headers['User-Agent'] = ''
+    icrs.append_user_agent('test-agent')
+    assert icrs.session.headers['User-Agent'] == 'test-agent'
+
+
+def test___init__with_additional_user_agent():
+    icrs = session.iControlRESTSession(
+        'admin',
+        'admin',
+        user_agent='test-agent/1.2.3'
+    )
+    assert icrs.session.headers['User-Agent'].endswith('test-agent/1.2.3')
+    assert 'f5-icontrol-rest-python' in icrs.session.headers['User-Agent']