Skip to content

Addressing multiple jenkins_plugins module issue #10346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

YoussefKhalidAli
Copy link
Contributor

@YoussefKhalidAli YoussefKhalidAli commented Jul 4, 2025

Fix jenkins_plugin issues. This PR addresses 3 different bugs in jenkins_plugin module.

1: #854
This fixes a bug where the jenkins_plugin module installs the latest version of a plugin, even if it was incompatible with the running Jenkins server version.
The fix ensures that when a specific plugin version is latest, the module now selects the latest plugin version that is compatible with the Jenkins server.
Compatibility is determined by comparing available plugin metadata against the current Jenkins version.

2: #4995
This is an issue with plugin dependencies not installing when version is set. This is technically not a bug since jenkins_plugin doesn't install dependencies if version is set.

with_dependencies:
description:
- Defines whether to install plugin dependencies.
- This option takes effect only if the O(version) is not defined.
type: bool
default: true

It was unintuitive for users expecting dependencies to be installed regardless.
This updates the module to install dependencies when with_dependencies=true, even if a specific version is set.

3: #4419
This is a bug with reaching the custom updates_url. This module uses url_username and url_password (intended for Jenkins), so when a request is made with fetch_url, it uses those credentials for that request, which causes an authentication error.
This PR introduces two new parameters: updates_url_username and updates_url_password, allowing users to provide separate credentials for the plugin update source. It also dynamically assigns force_basic_auth to avoid assigning url_* to every request based on target URL.

  • Bugfix Pull Request

jenkins_plugin

@ansibullbot
Copy link
Collaborator

cc @jtyr
click here for bot help

@ansibullbot ansibullbot added bug This issue/PR relates to a bug module module plugins plugin (any type) labels Jul 4, 2025
@YoussefKhalidAli YoussefKhalidAli marked this pull request as draft July 4, 2025 21:46
@ansibullbot ansibullbot added the WIP Work in progress label Jul 4, 2025
@ansibullbot ansibullbot added tests tests unit tests/unit labels Jul 6, 2025
@YoussefKhalidAli YoussefKhalidAli marked this pull request as ready for review July 6, 2025 02:48
@ansibullbot ansibullbot removed the WIP Work in progress label Jul 6, 2025
@felixfontein felixfontein added check-before-release PR will be looked at again shortly before release and merged if possible. backport-11 Automatically create a backport for the stable-10 branch backport-10 Automatically create a backport for the stable-10 branch and removed backport-10 Automatically create a backport for the stable-10 branch labels Jul 6, 2025
Copy link
Collaborator

@felixfontein felixfontein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution!

This PR introduces two new parameters: updates_url_username and updates_url_password, allowing users to provide separate credentials for the plugin update source. It also replaces fetch_url() with open_url() to update site requests to ensure that Jenkins server credentials are not unintentionally applied to those requests.

Right now url_username and url_password are used for both requests (I think), so changing that is a breaking change and won't get merged. You need a fallback to using url_username and url_password if no credentials are specified for the new options.


from ansible.module_utils.basic import AnsibleModule, to_bytes
from ansible.module_utils.six.moves import http_cookiejar as cookiejar
from ansible.module_utils.six.moves.urllib.parse import urlencode
from ansible.module_utils.urls import fetch_url, url_argument_spec
from ansible.module_utils.urls import fetch_url, url_argument_spec, open_url
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that changing from fetch_url to open_url can easily introducing breaking changes, since fetch_url does quite a few things implicitly (and what it does can also depend on the ansible-core version). Switching while still using the ansible.builtin.url docs fragment and the url_argument_spec() function is generally a very bad idea, since you then need to handle all changes to ansible-core in this collection as well. I would really stick with fetch_url. (You then have to modify module.params to pass over the right credentials, though...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reverted the changes back to fetch_url. I want to avoid modifying the module.params unless absolutely necessary, like adding the updates_url_* parameters. To get around the credentials injection, I set force_basic_auth=False when making requests to non-Jenkins servers. I replicated the issue by setting up my own Nexus server and used it as updates_url. This method worked, but I’m not sure if using force_basic_auth=False like this is the preferred solution. Would it be better to keep force_basic_auth=True and instead refactor how credentials are handled or is this an acceptable approach?

Copy link
Contributor Author

@YoussefKhalidAli YoussefKhalidAli Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe set force_basic_auth=True and save the Jenkins and external credentials internally and switch between them by dynamically by setting url_username and url_password based on the target server.

Copy link
Collaborator

@russoz russoz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi @YoussefKhalidAli

Thanks for your continued submissions, those are much appreciated. Here a couple of initial comments - I glanced very quickly through the PR though, so possibly more to come later on.

Comment on lines 522 to 523
dep_module = self.module
dep_module.params = dep_params
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be completely replacing the module's params with this customised dict. I would not recommend that.

Comment on lines 689 to 708
try: # Check if file is saved localy
with open(cache_path, "r") as f:
file_mtime = os.path.getmtime(cache_path)
now = time.time()
if now - file_mtime < 86400:
plugin_data = json.load(f)
else:
raise FileNotFoundError("Cache file is outdated.")
except Exception:
response, info = fetch_url(self.module, "https://updates.jenkins.io/current/plugin-versions.json") # Get list of plugins and their dependencies

if info['status'] != 200:
self.module.fail_json(msg="Failed to fetch plugin-versions.json", details=info)

try:
plugin_data = json.loads(to_native(response.read()), object_pairs_hook=OrderedDict)

# Save it to file for next time
with open(cache_path, "w") as f:
json.dump(plugin_data, f)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking the file's mtime after it's been open makes no sense.

This would perhaps look better as:

Suggested change
try: # Check if file is saved localy
with open(cache_path, "r") as f:
file_mtime = os.path.getmtime(cache_path)
now = time.time()
if now - file_mtime < 86400:
plugin_data = json.load(f)
else:
raise FileNotFoundError("Cache file is outdated.")
except Exception:
response, info = fetch_url(self.module, "https://updates.jenkins.io/current/plugin-versions.json") # Get list of plugins and their dependencies
if info['status'] != 200:
self.module.fail_json(msg="Failed to fetch plugin-versions.json", details=info)
try:
plugin_data = json.loads(to_native(response.read()), object_pairs_hook=OrderedDict)
# Save it to file for next time
with open(cache_path, "w") as f:
json.dump(plugin_data, f)
try: # Check if file is saved localy
file_mtime = os.path.getmtime(cache_path)
now = time.time()
if now - file_mtime >= 86400:
response, info = fetch_url(self.module, "https://updates.jenkins.io/current/plugin-versions.json")
if info['status'] != 200:
self.module.fail_json(msg="Failed to fetch plugin-versions.json", details=info)
plugin_data = json.loads(to_native(response.read()), object_pairs_hook=OrderedDict)
# Save it to file for next time
with open(cache_path, "w") as f:
json.dump(plugin_data, f)
with open(cache_path, "r") as f:
plugin_data = json.load(f)
except Exception:
self.module.fail_json(...)

Copy link
Collaborator

@russoz russoz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @YoussefKhalidAli sorry I wasn't fast enough, I started this review earlier today but got sidetracked.

I spotted a typo and something else that could need a fix.

@@ -860,7 +1010,7 @@ def main():
changed = jp.disable()

# Print status of the change
module.exit_json(changed=changed, plugin=name, state=state)
module.exit_json(changed=changed, plugin=name, state=state, dependencies=jp.dependencies_states if hasattr(jp, 'dependencies_states') else None)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified to:

Suggested change
module.exit_json(changed=changed, plugin=name, state=state, dependencies=jp.dependencies_states if hasattr(jp, 'dependencies_states') else None)
module.exit_json(changed=changed, plugin=name, state=state, dependencies=getattr(jp, 'dependencies_states', None))

Copy link
Collaborator

@russoz russoz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a suggestion but is not a show stopper. LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport-11 Automatically create a backport for the stable-10 branch bug This issue/PR relates to a bug check-before-release PR will be looked at again shortly before release and merged if possible. module module plugins plugin (any type) tests tests unit tests/unit
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants