Skip to content

Commit 001455b

Browse files
Return to fetch_url
1 parent 6414ae1 commit 001455b

File tree

3 files changed

+103
-64
lines changed

3 files changed

+103
-64
lines changed
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
bugfixes:
2-
- "jenkins plugins plugin - install latest compatible version instead of latest (https://github.com/ansible-collections/community.general/pull/10346)."
3-
- "jenkins plugins plugin - seperate Jenkins and external url credentials (https://github.com/ansible-collections/community.general/pull/10346)."
2+
- "jenkins_plugin - install latest compatible version instead of latest (https://github.com/ansible-collections/community.general/issues/854)."
3+
- "jenkins_plugin - seperate Jenkins and external URL credentials (https://github.com/ansible-collections/community.general/issues/4419)."
44

55
minor_changes:
6-
- "jenkins plugins plugin - install dependencies for specific version (https://github.com/ansible-collections/community.general/pull/10346)."
6+
- "jenkins_plugin - install dependencies for specific version (https://github.com/ansible-collections/community.general/issue/4995)."

plugins/modules/jenkins_plugin.py

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,16 @@
7676
default: ['https://updates.jenkins.io', 'http://mirrors.jenkins.io']
7777
updates_url_username:
7878
description:
79-
- If using a custom O(updates_url), set this as the username of the user with access to the url.
79+
- If using a custom O(updates_url), set this as the username of the user with access to the URL.
8080
- If the custom O(updates_url) does not require authentication, this can be left empty.
8181
type: str
82+
version_added: 11.1.0
8283
updates_url_password:
8384
description:
84-
- If using a custom O(updates_url), set this as the password of the user with access to the url.
85+
- If using a custom O(updates_url), set this as the password of the user with access to the URL.
8586
- If the custom O(updates_url) does not require authentication, this can be left empty.
8687
type: str
88+
version_added: 11.1.0
8789
update_json_url_segment:
8890
type: list
8991
elements: str
@@ -122,6 +124,8 @@
122124
with_dependencies:
123125
description:
124126
- Defines whether to install plugin dependencies.
127+
- In earlier versions, this option had no effect when a specific C(version) was set.
128+
- Since community.general 11.1.0, dependencies are also installed for versioned plugins.
125129
type: bool
126130
default: true
127131
@@ -325,13 +329,12 @@
325329
import os
326330
import tempfile
327331
import time
328-
import base64
329332
from collections import OrderedDict
330333

331334
from ansible.module_utils.basic import AnsibleModule, to_bytes
332335
from ansible.module_utils.six.moves import http_cookiejar as cookiejar
333336
from ansible.module_utils.six.moves.urllib.parse import urlencode
334-
from ansible.module_utils.urls import fetch_url, url_argument_spec, open_url
337+
from ansible.module_utils.urls import fetch_url, url_argument_spec, basic_auth_header
335338
from ansible.module_utils.six import text_type, binary_type
336339
from ansible.module_utils.common.text.converters import to_native
337340

@@ -355,18 +358,14 @@ def __init__(self, module):
355358
# Authentication for non-Jenkins calls
356359
self.updates_url_credentials = {}
357360
if self.params.get('updates_url_username') and self.params.get('updates_url_password'):
358-
auth = "{}:{}".format(self.params['updates_url_username'], self.params['updates_url_password']).encode("utf-8")
359-
b64_auth = base64.b64encode(auth).decode("ascii")
360-
self.updates_url_credentials["Authorization"] = "Basic {}".format(b64_auth)
361+
self.updates_url_credentials["Authorization"] = basic_auth_header(self.params['updates_url_username'], self.params['updates_url_password'])
361362

362363
# Crumb
363364
self.crumb = {}
364365

365366
# Authentication for Jenkins calls
366367
if self.params.get('url_username') and self.params.get('url_password'):
367-
auth = "{}:{}".format(self.params['url_username'], self.params['url_password']).encode("utf-8")
368-
b64_auth = base64.b64encode(auth).decode("ascii")
369-
self.crumb["Authorization"] = "Basic {}".format(b64_auth)
368+
self.crumb["Authorization"] = basic_auth_header(self.params['url_username'], self.params['url_password'])
370369

371370
# Cookie jar for crumb session
372371
self.cookies = None
@@ -418,16 +417,18 @@ def _get_urls_data(self, urls, what=None, msg_status=None, msg_exception=None, *
418417
self.module.debug("fetching url: %s" % url)
419418

420419
is_jenkins_call = url.startswith(self.url)
420+
self.module.params['force_basic_auth'] = is_jenkins_call
421421

422-
response = open_url(
423-
url, timeout=self.timeout,
424-
cookies=self.cookies if is_jenkins_call else None,
425-
headers=self.crumb if is_jenkins_call else self.updates_url_credentials, **kwargs)
426-
if response.getcode() == 200:
422+
response, info = fetch_url(
423+
self.module, url, timeout=self.timeout, cookies=self.cookies,
424+
headers=self.crumb if is_jenkins_call else self.updates_url_credentials or self.crumb,
425+
**kwargs)
426+
if info['status'] == 200:
427427
return response
428428
else:
429429
err_msg = ("%s. fetching url %s failed. response code: %s" % (msg_status, url, response.getcode()))
430-
430+
if info['status'] > 400: # extend error message
431+
err_msg = "%s. response body: %s" % (err_msg, info['body'])
431432
except Exception as e:
432433
err_msg = "%s. fetching url %s failed. error msg: %s" % (msg_status, url, to_native(e))
433434
finally:
@@ -451,19 +452,18 @@ def _get_url_data(
451452
# Get the URL data
452453
try:
453454
is_jenkins_call = url.startswith(self.url)
454-
response = open_url(
455-
url, timeout=self.timeout,
456-
cookies=self.cookies if is_jenkins_call else None,
457-
headers=self.crumb if is_jenkins_call else self.updates_url_credentials, **kwargs)
455+
self.module.params['force_basic_auth'] = is_jenkins_call
456+
457+
response, info = fetch_url(
458+
self.module, url, timeout=self.timeout, cookies=self.cookies,
459+
headers=self.crumb if is_jenkins_call else self.updates_url_credentials or self.crumb,
460+
**kwargs)
458461

459-
if response.getcode() != 200:
462+
if info['status'] != 200:
460463
if dont_fail:
461464
raise FailedInstallingWithPluginManager("HTTP {}".format(response.getcode()))
462465
else:
463-
self.module.fail_json(
464-
msg=msg_status,
465-
details="Received status code {} from {}".format(response.getcode(), url)
466-
)
466+
self.module.fail_json(msg=msg_status, details=info['msg'], info=info)
467467
except Exception as e:
468468
if dont_fail:
469469
raise FailedInstallingWithPluginManager(e)
@@ -679,6 +679,7 @@ def _get_latest_plugin_urls(self):
679679

680680
def _get_latest_compatible_plugin_version(self, plugin_name=None):
681681
if not hasattr(self, 'jenkins_version'):
682+
self.module.params['force_basic_auth'] = True
682683
resp, info = fetch_url(self.module, self.url)
683684
raw_version = info.get("x-jenkins")
684685
self.jenkins_version = self.parse_version(raw_version)
@@ -694,9 +695,9 @@ def _get_latest_compatible_plugin_version(self, plugin_name=None):
694695
else:
695696
raise FileNotFoundError("Cache file is outdated.")
696697
except Exception:
697-
response = open_url("https://updates.jenkins.io/current/plugin-versions.json") # Get list of plugins and their dependencies
698+
response, info = fetch_url(self.module, "https://updates.jenkins.io/current/plugin-versions.json") # Get list of plugins and their dependencies
698699

699-
if response.getcode() != 200:
700+
if info['status'] != 200:
700701
self.module.fail_json(msg="Failed to fetch plugin-versions.json", details=info)
701702

702703
try:
@@ -949,9 +950,6 @@ def main():
949950
supports_check_mode=True,
950951
)
951952

952-
# Force basic authentication
953-
module.params['force_basic_auth'] = True
954-
955953
# Convert timeout to float
956954
try:
957955
module.params['timeout'] = float(module.params['timeout'])

tests/unit/plugins/modules/test_jenkins_plugin.py

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77

88
from io import BytesIO
99
import json
10-
import socket
1110
from collections import OrderedDict
1211

1312
from ansible_collections.community.general.plugins.modules.jenkins_plugin import JenkinsPlugin
@@ -16,6 +15,7 @@
1615
MagicMock,
1716
patch,
1817
)
18+
from ansible.module_utils.urls import basic_auth_header
1919

2020

2121
def pass_function(*args, **kwargs):
@@ -199,9 +199,8 @@ def isInList(l, i):
199199
return False
200200

201201

202-
@patch("ansible_collections.community.general.plugins.modules.jenkins_plugin.open_url")
203202
@patch("ansible_collections.community.general.plugins.modules.jenkins_plugin.fetch_url")
204-
def test__get_latest_compatible_plugin_version(fetch_mock, open_mock, mocker):
203+
def test__get_latest_compatible_plugin_version(fetch_mock, mocker):
205204
"test the latest compatible plugin version retrieval"
206205

207206
params = {
@@ -216,37 +215,79 @@ def test__get_latest_compatible_plugin_version(fetch_mock, open_mock, mocker):
216215
module = mocker.Mock()
217216
module.params = params
218217

219-
mock_response = MagicMock()
220-
mock_response.read.return_value = b""
221-
fetch_mock.return_value = (mock_response, {"x-jenkins": "2.263.1"})
222-
223-
try:
224-
socket.gethostbyname("updates.jenkins.io")
225-
online = True
226-
except socket.gaierror:
227-
online = False
228-
229-
# Mock the open_url to simulate the response from Jenkins update center if tests are run offline
230-
if not online:
231-
plugin_data = {
232-
"plugins": {
233-
"git": OrderedDict([
234-
("4.8.2", {"requiredCore": "2.263.1"}),
235-
("4.8.3", {"requiredCore": "2.263.1"}),
236-
("4.9.0", {"requiredCore": "2.289.1"}),
237-
("4.9.1", {"requiredCore": "2.289.1"}),
238-
])
239-
}
218+
jenkins_info = {"x-jenkins": "2.263.1"}
219+
jenkins_response = MagicMock()
220+
jenkins_response.read.return_value = b"{}"
221+
222+
plugin_data = {
223+
"plugins": {
224+
"git": OrderedDict([
225+
("4.8.2", {"requiredCore": "2.263.1"}),
226+
("4.8.3", {"requiredCore": "2.263.1"}),
227+
("4.9.0", {"requiredCore": "2.289.1"}),
228+
("4.9.1", {"requiredCore": "2.289.1"}),
229+
])
240230
}
241-
mock_open_resp = MagicMock()
242-
mock_open_resp.getcode.return_value = 200
243-
mock_open_resp.read.return_value = json.dumps(plugin_data).encode("utf-8")
244-
open_mock.return_value = mock_open_resp
231+
}
232+
plugin_versions_response = MagicMock()
233+
plugin_versions_response.read.return_value = json.dumps(plugin_data).encode("utf-8")
234+
plugin_versions_info = {"status": 200}
245235

246-
JenkinsPlugin._csrf_enabled = pass_function
247-
JenkinsPlugin._get_installed_plugins = pass_function
236+
def fetch_url_side_effect(module, url, **kwargs):
237+
if "plugin-versions.json" in url:
238+
return (plugin_versions_response, plugin_versions_info)
239+
else:
240+
return (jenkins_response, jenkins_info)
248241

249-
jenkins_plugin = JenkinsPlugin(module)
242+
fetch_mock.side_effect = fetch_url_side_effect
243+
244+
JenkinsPlugin._csrf_enabled = lambda self: False
245+
JenkinsPlugin._get_installed_plugins = lambda self: None
250246

247+
jenkins_plugin = JenkinsPlugin(module)
251248
latest_version = jenkins_plugin._get_latest_compatible_plugin_version()
252249
assert latest_version == '4.8.3'
250+
251+
252+
@patch("ansible_collections.community.general.plugins.modules.jenkins_plugin.fetch_url")
253+
def test__get_urls_data_sets_correct_headers(fetch_mock, mocker):
254+
params = {
255+
"url": "http://jenkins.example.com",
256+
"timeout": 30,
257+
"name": "git",
258+
"jenkins_home": "/var/lib/jenkins",
259+
"updates_url": ["http://updates.example.com"],
260+
"latest_plugins_url_segments": ["latest"],
261+
"update_json_url_segment": ["update-center.json"],
262+
"versioned_plugins_url_segments": ["plugins"],
263+
"url_username": "jenkins_user",
264+
"url_password": "jenkins_pass",
265+
"updates_url_username": "update_user",
266+
"updates_url_password": "update_pass",
267+
}
268+
module = mocker.Mock()
269+
module.params = params
270+
271+
dummy_response = MagicMock()
272+
fetch_mock.return_value = (dummy_response, {"status": 200})
273+
274+
JenkinsPlugin._csrf_enabled = lambda self: False
275+
JenkinsPlugin._get_installed_plugins = lambda self: None
276+
277+
jp = JenkinsPlugin(module)
278+
279+
update_url = "http://updates.example.com/plugin-versions.json"
280+
jp._get_urls_data([update_url])
281+
282+
jenkins_url = "http://jenkins.example.com/some-endpoint"
283+
jp._get_urls_data([jenkins_url])
284+
285+
calls = fetch_mock.call_args_list
286+
287+
dummy, kwargs_2 = calls[1]
288+
jenkins_auth = basic_auth_header("jenkins_user", "jenkins_pass")
289+
assert kwargs_2["headers"]["Authorization"] == jenkins_auth
290+
291+
dummy, kwargs_1 = calls[0]
292+
updates_auth = basic_auth_header("update_user", "update_pass")
293+
assert kwargs_1["headers"]["Authorization"] == updates_auth

0 commit comments

Comments
 (0)