From eaba5572cd1f206ae850c6730d50f32c58cc3131 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Fri, 6 Dec 2019 10:34:14 +1000 Subject: [PATCH] ansible-galaxy - expand User-Agent string for Galaxy endpoints (#65578) --- .../fragments/ansible-galaxy-agent.yaml | 2 ++ lib/ansible/galaxy/api.py | 6 ++--- lib/ansible/galaxy/collection.py | 4 ++-- lib/ansible/galaxy/login.py | 7 +++--- lib/ansible/galaxy/role.py | 3 ++- lib/ansible/galaxy/token.py | 4 +++- lib/ansible/galaxy/user_agent.py | 23 +++++++++++++++++++ test/units/galaxy/test_api.py | 6 +++++ test/units/galaxy/test_user_agent.py | 18 +++++++++++++++ 9 files changed, 63 insertions(+), 10 deletions(-) create mode 100644 changelogs/fragments/ansible-galaxy-agent.yaml create mode 100644 lib/ansible/galaxy/user_agent.py create mode 100644 test/units/galaxy/test_user_agent.py diff --git a/changelogs/fragments/ansible-galaxy-agent.yaml b/changelogs/fragments/ansible-galaxy-agent.yaml new file mode 100644 index 00000000000..b18fb910430 --- /dev/null +++ b/changelogs/fragments/ansible-galaxy-agent.yaml @@ -0,0 +1,2 @@ +bugfixes: +- ansible-galaxy - Expand the ``User-Agent`` to include more information and add it to more calls to Galaxy endpoints. diff --git a/lib/ansible/galaxy/api.py b/lib/ansible/galaxy/api.py index 73b240339b7..4f4cf30bc2e 100644 --- a/lib/ansible/galaxy/api.py +++ b/lib/ansible/galaxy/api.py @@ -13,7 +13,7 @@ import time from ansible import context from ansible.errors import AnsibleError -from ansible.module_utils.ansible_release import __version__ as ansible_version +from ansible.galaxy.user_agent import user_agent from ansible.module_utils.six import string_types from ansible.module_utils.six.moves.urllib.error import HTTPError from ansible.module_utils.six.moves.urllib.parse import quote as urlquote, urlencode, urlparse @@ -185,7 +185,7 @@ class GalaxyAPI: try: display.vvvv("Calling Galaxy at %s" % url) resp = open_url(to_native(url), data=args, validate_certs=self.validate_certs, headers=headers, - method=method, timeout=20, http_agent='ansible-galaxy/%s' % ansible_version) + method=method, timeout=20, http_agent=user_agent()) except HTTPError as e: raise GalaxyError(e, error_context_msg) except Exception as e: @@ -219,7 +219,7 @@ class GalaxyAPI: """ url = _urljoin(self.api_server, self.available_api_versions['v1'], "tokens") + '/' args = urlencode({"github_token": github_token}) - resp = open_url(url, data=args, validate_certs=self.validate_certs, method="POST") + resp = open_url(url, data=args, validate_certs=self.validate_certs, method="POST", http_agent=user_agent()) data = json.loads(to_text(resp.read(), errors='surrogate_or_strict')) return data diff --git a/lib/ansible/galaxy/collection.py b/lib/ansible/galaxy/collection.py index ad76e4263d3..445cbb24661 100644 --- a/lib/ansible/galaxy/collection.py +++ b/lib/ansible/galaxy/collection.py @@ -31,9 +31,9 @@ import ansible.constants as C from ansible.errors import AnsibleError from ansible.galaxy import get_collections_galaxy_meta_info from ansible.galaxy.api import CollectionVersionMetadata, GalaxyError +from ansible.galaxy.user_agent import user_agent from ansible.module_utils import six from ansible.module_utils._text import to_bytes, to_native, to_text -from ansible.module_utils.ansible_release import __version__ as ansible_version from ansible.utils.collection_loader import AnsibleCollectionRef from ansible.utils.display import Display from ansible.utils.hashing import secure_hash, secure_hash_s @@ -877,7 +877,7 @@ def _download_file(url, b_path, expected_hash, validate_certs, headers=None): display.vvv("Downloading %s to %s" % (url, to_text(b_path))) # Galaxy redirs downloads to S3 which reject the request if an Authorization header is attached so don't redir that resp = open_url(to_native(url, errors='surrogate_or_strict'), validate_certs=validate_certs, headers=headers, - unredirected_headers=['Authorization'], http_agent='ansible-galaxy/%s' % ansible_version) + unredirected_headers=['Authorization'], http_agent=user_agent()) with open(b_file_path, 'wb') as download_file: data = resp.read(bufsize) diff --git a/lib/ansible/galaxy/login.py b/lib/ansible/galaxy/login.py index 1c1c37b642e..3f9487daf1f 100644 --- a/lib/ansible/galaxy/login.py +++ b/lib/ansible/galaxy/login.py @@ -27,6 +27,7 @@ import json from ansible import context from ansible.errors import AnsibleError +from ansible.galaxy.user_agent import user_agent from ansible.module_utils.six.moves import input from ansible.module_utils.six.moves.urllib.error import HTTPError from ansible.module_utils.urls import open_url @@ -80,7 +81,7 @@ class GalaxyLogin(object): try: tokens = json.load(open_url(self.GITHUB_AUTH, url_username=self.github_username, url_password=self.github_password, force_basic_auth=True, - validate_certs=self._validate_certs)) + validate_certs=self._validate_certs, http_agent=user_agent())) except HTTPError as e: res = json.load(e) raise AnsibleError(res['message']) @@ -91,7 +92,7 @@ class GalaxyLogin(object): try: open_url('https://api.github.com/authorizations/%d' % token['id'], url_username=self.github_username, url_password=self.github_password, method='DELETE', - force_basic_auth=True, validate_certs=self._validate_certs) + force_basic_auth=True, validate_certs=self._validate_certs, http_agent=user_agent()) except HTTPError as e: res = json.load(e) raise AnsibleError(res['message']) @@ -105,7 +106,7 @@ class GalaxyLogin(object): try: data = json.load(open_url(self.GITHUB_AUTH, url_username=self.github_username, url_password=self.github_password, force_basic_auth=True, data=args, - validate_certs=self._validate_certs)) + validate_certs=self._validate_certs, http_agent=user_agent())) except HTTPError as e: res = json.load(e) raise AnsibleError(res['message']) diff --git a/lib/ansible/galaxy/role.py b/lib/ansible/galaxy/role.py index c82a022a527..0589562d143 100644 --- a/lib/ansible/galaxy/role.py +++ b/lib/ansible/galaxy/role.py @@ -33,6 +33,7 @@ from shutil import rmtree from ansible import context from ansible.errors import AnsibleError +from ansible.galaxy.user_agent import user_agent from ansible.module_utils._text import to_native, to_text from ansible.module_utils.urls import open_url from ansible.playbook.role.requirement import RoleRequirement @@ -179,7 +180,7 @@ class GalaxyRole(object): display.display("- downloading role from %s" % archive_url) try: - url_file = open_url(archive_url, validate_certs=self._validate_certs) + url_file = open_url(archive_url, validate_certs=self._validate_certs, http_agent=user_agent()) temp_file = tempfile.NamedTemporaryFile(delete=False) data = url_file.read() while data: diff --git a/lib/ansible/galaxy/token.py b/lib/ansible/galaxy/token.py index b0c61933420..31f2d88e744 100644 --- a/lib/ansible/galaxy/token.py +++ b/lib/ansible/galaxy/token.py @@ -29,6 +29,7 @@ from stat import S_IRUSR, S_IWUSR import yaml from ansible import constants as C +from ansible.galaxy.user_agent import user_agent from ansible.module_utils._text import to_bytes, to_native, to_text from ansible.module_utils.urls import open_url from ansible.utils.display import Display @@ -76,7 +77,8 @@ class KeycloakToken(object): resp = open_url(to_native(self.auth_url), data=payload, validate_certs=self.validate_certs, - method='POST') + method='POST', + http_agent=user_agent()) # TODO: handle auth errors diff --git a/lib/ansible/galaxy/user_agent.py b/lib/ansible/galaxy/user_agent.py new file mode 100644 index 00000000000..c860bcdb612 --- /dev/null +++ b/lib/ansible/galaxy/user_agent.py @@ -0,0 +1,23 @@ +# Copyright: (c) 2019, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import platform +import sys + +from ansible.module_utils.ansible_release import __version__ as ansible_version + + +def user_agent(): + """Returns a user agent used by ansible-galaxy to include the Ansible version, platform and python version.""" + + python_version = sys.version_info + return u"ansible-galaxy/{ansible_version} ({platform}; python:{py_major}.{py_minor}.{py_micro})".format( + ansible_version=ansible_version, + platform=platform.system(), + py_major=python_version.major, + py_minor=python_version.minor, + py_micro=python_version.micro, + ) diff --git a/test/units/galaxy/test_api.py b/test/units/galaxy/test_api.py index 86cfaeaa6f4..99c55974234 100644 --- a/test/units/galaxy/test_api.py +++ b/test/units/galaxy/test_api.py @@ -158,7 +158,9 @@ def test_initialise_galaxy(monkeypatch): assert actual == {u'token': u'my token'} assert mock_open.call_count == 2 assert mock_open.mock_calls[0][1][0] == 'https://galaxy.ansible.com/api/' + assert 'ansible-galaxy' in mock_open.mock_calls[0][2]['http_agent'] assert mock_open.mock_calls[1][1][0] == 'https://galaxy.ansible.com/api/v1/tokens/' + assert 'ansible-galaxy' in mock_open.mock_calls[1][2]['http_agent'] assert mock_open.mock_calls[1][2]['data'] == 'github_token=github_token' @@ -179,7 +181,9 @@ def test_initialise_galaxy_with_auth(monkeypatch): assert actual == {u'token': u'my token'} assert mock_open.call_count == 2 assert mock_open.mock_calls[0][1][0] == 'https://galaxy.ansible.com/api/' + assert 'ansible-galaxy' in mock_open.mock_calls[0][2]['http_agent'] assert mock_open.mock_calls[1][1][0] == 'https://galaxy.ansible.com/api/v1/tokens/' + assert 'ansible-galaxy' in mock_open.mock_calls[1][2]['http_agent'] assert mock_open.mock_calls[1][2]['data'] == 'github_token=github_token' @@ -201,6 +205,7 @@ def test_initialise_automation_hub(monkeypatch): assert api.available_api_versions['v3'] == u'v3/' assert mock_open.mock_calls[0][1][0] == 'https://galaxy.ansible.com/api/' + assert 'ansible-galaxy' in mock_open.mock_calls[0][2]['http_agent'] assert mock_open.mock_calls[0][2]['headers'] == {'Authorization': 'Bearer my_token'} @@ -235,6 +240,7 @@ def test_get_available_api_versions(monkeypatch): assert mock_open.call_count == 1 assert mock_open.mock_calls[0][1][0] == 'https://galaxy.ansible.com/api/' + assert 'ansible-galaxy' in mock_open.mock_calls[0][2]['http_agent'] def test_publish_collection_missing_file(): diff --git a/test/units/galaxy/test_user_agent.py b/test/units/galaxy/test_user_agent.py new file mode 100644 index 00000000000..da0103f3956 --- /dev/null +++ b/test/units/galaxy/test_user_agent.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +# Copyright: (c) 2019, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import platform + +from ansible.galaxy import user_agent +from ansible.module_utils.ansible_release import __version__ as ansible_version + + +def test_user_agent(): + res = user_agent.user_agent() + assert res.startswith('ansible-galaxy/%s' % ansible_version) + assert platform.system() in res + assert 'python:' in res