From f403aaf4fa9221f393b630a7cd1c666eae9b294c Mon Sep 17 00:00:00 2001 From: Slavek Kabrda Date: Wed, 1 Jul 2020 19:37:45 +0200 Subject: [PATCH 1/3] Improve user-agent header to include telemetry information --- datadog/api/http_client.py | 9 ++++ .../TestDatadog.test_user_agent.yaml | 51 +++++++++++++++++++ tests/integration/api/test_api.py | 8 +++ tox.ini | 2 +- 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 tests/integration/api/cassettes/TestDatadog.test_user_agent.yaml diff --git a/datadog/api/http_client.py b/datadog/api/http_client.py index fa5be4280..623657953 100644 --- a/datadog/api/http_client.py +++ b/datadog/api/http_client.py @@ -10,6 +10,7 @@ """ # stdlib import logging +import platform import urllib from threading import Lock @@ -76,9 +77,17 @@ def request(cls, method, url, headers, params, data, timeout, proxies, verify, m with cls._session_lock: if cls._session is None: + from datadog import version cls._session = requests.Session() http_adapter = requests.adapters.HTTPAdapter(max_retries=max_retries) cls._session.mount('https://', http_adapter) + user_agent = 'datadogpy/{version} (python {pyver}; os {os}; arch {arch})'.format( + version=version.__version__, + pyver=platform.python_version(), + os=platform.system().lower(), + arch=platform.machine().lower(), + ) + cls._session.headers.update({'User-Agent': user_agent}) result = cls._session.request( method, url, diff --git a/tests/integration/api/cassettes/TestDatadog.test_user_agent.yaml b/tests/integration/api/cassettes/TestDatadog.test_user_agent.yaml new file mode 100644 index 000000000..6990a10a9 --- /dev/null +++ b/tests/integration/api/cassettes/TestDatadog.test_user_agent.yaml @@ -0,0 +1,51 @@ +interactions: +- request: + body: null + headers: + Accept: + - '*/*' + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - datadogpy/0.38.0.dev (python 3.7.7; os darwin; arch x86_64) + method: GET + uri: https://api.datadoghq.com/api/v1/validate + response: + body: + string: '{"valid":true}' + headers: + Cache-Control: + - no-cache + Connection: + - keep-alive + Content-Length: + - '14' + Content-Security-Policy: + - frame-ancestors 'self'; report-uri https://api.datadoghq.com/csp-report + Content-Type: + - application/json + DD-POOL: + - dogweb + Date: + - Wed, 01 Jul 2020 17:22:12 GMT + Pragma: + - no-cache + Set-Cookie: + - DD-PSHARD=89; Max-Age=604800; Path=/; expires=Wed, 08-Jul-2020 17:22:12 GMT; + secure; HttpOnly + Strict-Transport-Security: + - max-age=15724800; + X-Content-Type-Options: + - nosniff + X-DD-Debug: + - SaHvyR/hQzhMjBxXmmuM76vwlwfocpgL0LhX3u6R0CFONYqUGm7Xe/7/HyTliTFX + X-DD-VERSION: + - '35.2688709' + X-Frame-Options: + - SAMEORIGIN + status: + code: 200 + message: OK +version: 1 diff --git a/tests/integration/api/test_api.py b/tests/integration/api/test_api.py index 8bce7a605..c05806462 100644 --- a/tests/integration/api/test_api.py +++ b/tests/integration/api/test_api.py @@ -4,7 +4,9 @@ # python import datetime import json +import mock import os +import re import time import requests @@ -959,3 +961,9 @@ def test_roles_crud(self, dog): # check if new role is deleted successfully res = dog.Roles.get(role_uuid) assert "errors" in res + + @mock.patch('datadog.api._return_raw_response', True) + def test_user_agent(self, dog): + _, resp = dog.api_client.APIClient.submit('GET', 'validate') + assert re.match(r'^datadogpy\/[^\s]+ \(python [^\s]+; os [^\s]+; arch [^\s]+\)$', resp.request.headers['User-Agent']) + resp.request.headers['User-Agent'] diff --git a/tox.ini b/tox.ini index 31e054a44..1277dcd67 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ envlist = passenv = DD_TEST_CLIENT* usedevelop = true deps = - !integration: mock + mock click freezegun pytest From 432143b16f07cc012b4866ee62a66ee2b1c9d4bb Mon Sep 17 00:00:00 2001 From: Slavek Kabrda Date: Wed, 1 Jul 2020 19:47:34 +0200 Subject: [PATCH 2/3] Add mock as dependency for all tests --- tox.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 1277dcd67..37ddc062c 100644 --- a/tox.ini +++ b/tox.ini @@ -11,9 +11,9 @@ envlist = passenv = DD_TEST_CLIENT* usedevelop = true deps = - mock click freezegun + mock pytest pytest-vcr python-dateutil @@ -28,6 +28,7 @@ usedevelop = true deps = click freezegun + mock pytest pytest-vcr python-dateutil From 67818c91d423f0a983fff3c5b5de5516ddf92b8d Mon Sep 17 00:00:00 2001 From: Slavek Kabrda Date: Thu, 2 Jul 2020 09:33:49 +0200 Subject: [PATCH 3/3] Also modify header for the URLFetchClient --- datadog/api/http_client.py | 24 +++++++++++++++--------- tests/integration/api/test_api.py | 1 - 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/datadog/api/http_client.py b/datadog/api/http_client.py index 623657953..6d1f599d9 100644 --- a/datadog/api/http_client.py +++ b/datadog/api/http_client.py @@ -9,6 +9,7 @@ 2. `urlfetch` 3p module - Google App Engine only """ # stdlib +import copy import logging import platform import urllib @@ -33,6 +34,16 @@ log = logging.getLogger('datadog.api') +def _get_user_agent_header(): + from datadog import version + return 'datadogpy/{version} (python {pyver}; os {os}; arch {arch})'.format( + version=version.__version__, + pyver=platform.python_version(), + os=platform.system().lower(), + arch=platform.machine().lower(), + ) + + def _remove_context(exc): """Python3: remove context from chained exceptions to prevent leaking API keys in tracebacks.""" exc.__cause__ = None @@ -77,17 +88,10 @@ def request(cls, method, url, headers, params, data, timeout, proxies, verify, m with cls._session_lock: if cls._session is None: - from datadog import version cls._session = requests.Session() http_adapter = requests.adapters.HTTPAdapter(max_retries=max_retries) cls._session.mount('https://', http_adapter) - user_agent = 'datadogpy/{version} (python {pyver}; os {os}; arch {arch})'.format( - version=version.__version__, - pyver=platform.python_version(), - os=platform.system().lower(), - arch=platform.machine().lower(), - ) - cls._session.headers.update({'User-Agent': user_agent}) + cls._session.headers.update({'User-Agent': _get_user_agent_header()}) result = cls._session.request( method, url, @@ -139,12 +143,14 @@ def request(cls, method, url, headers, params, data, timeout, proxies, verify, m url=url, params=urllib.urlencode(params) ) + newheaders = copy.deepcopy(headers) + newheaders['User-Agent'] = _get_user_agent_header() try: result = urlfetch.fetch( url=url_with_params, method=method, - headers=headers, + headers=newheaders, validate_certificate=validate_certificate, deadline=timeout, payload=data, diff --git a/tests/integration/api/test_api.py b/tests/integration/api/test_api.py index c05806462..b69c7a434 100644 --- a/tests/integration/api/test_api.py +++ b/tests/integration/api/test_api.py @@ -966,4 +966,3 @@ def test_roles_crud(self, dog): def test_user_agent(self, dog): _, resp = dog.api_client.APIClient.submit('GET', 'validate') assert re.match(r'^datadogpy\/[^\s]+ \(python [^\s]+; os [^\s]+; arch [^\s]+\)$', resp.request.headers['User-Agent']) - resp.request.headers['User-Agent']