1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2026-02-04 07:51:50 +00:00
community.general/plugins/modules/gem.py
patchback[bot] b5d57a35d6
[PR #11442/72220a2b backport][stable-12] fix gem module compatibility with ruby-4-rubygems (#11452)
fix gem module compatibility with ruby-4-rubygems (#11442)

* fix gem module compatibility with ruby-4-rubygems

rubygem's `query` command has recently been removed, see ruby/rubygems#9083.
address this by using the `list` command instead.

resolves #11397

* add changelog

* Adjust changelog fragment.

---------


(cherry picked from commit 72220a2b15)

Co-authored-by: glaszig <mail+github@glasz.org>
Co-authored-by: Felix Fontein <felix@fontein.de>
2026-01-26 17:43:07 +01:00

342 lines
10 KiB
Python

#!/usr/bin/python
# Copyright (c) 2013, Johan Wiren <johan.wiren.se@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import annotations
DOCUMENTATION = r"""
module: gem
short_description: Manage Ruby gems
description:
- Manage installation and uninstallation of Ruby gems.
extends_documentation_fragment:
- community.general.attributes
attributes:
check_mode:
support: full
diff_mode:
support: none
options:
name:
type: str
description:
- The name of the gem to be managed.
required: true
state:
type: str
description:
- The desired state of the gem. V(latest) ensures that the latest version is installed.
choices: [present, absent, latest]
default: present
gem_source:
type: path
description:
- The path to a local gem used as installation source.
include_dependencies:
description:
- Whether to include dependencies or not.
type: bool
default: true
repository:
type: str
description:
- The repository from which the gem is installed.
aliases: [source]
user_install:
description:
- Install gem in user's local gems cache or for all users.
type: bool
default: true
executable:
type: path
description:
- Override the path to the gem executable.
install_dir:
type: path
description:
- Install the gems into a specific directory. These gems are independent from the global installed ones. Specifying
this requires user_install to be false.
bindir:
type: path
description:
- Install executables into a specific directory.
version_added: 3.3.0
norc:
type: bool
default: true
description:
- Avoid loading any C(.gemrc) file. Ignored for RubyGems prior to 2.5.2.
- The default changed from V(false) to V(true) in community.general 6.0.0.
version_added: 3.3.0
env_shebang:
description:
- Rewrite the shebang line on installed scripts to use /usr/bin/env.
default: false
type: bool
version:
type: str
description:
- Version of the gem to be installed/removed.
pre_release:
description:
- Allow installation of pre-release versions of the gem.
default: false
type: bool
include_doc:
description:
- Install with or without docs.
default: false
type: bool
build_flags:
type: str
description:
- Allow adding build flags for gem compilation.
force:
description:
- Force gem to (un-)install, bypassing dependency checks.
default: false
type: bool
author:
- "Ansible Core Team"
- "Johan Wiren (@johanwiren)"
"""
EXAMPLES = r"""
- name: Install version 1.0 of vagrant
community.general.gem:
name: vagrant
version: 1.0
state: present
- name: Install latest available version of rake
community.general.gem:
name: rake
state: latest
- name: Install rake version 1.0 from a local gem on disk
community.general.gem:
name: rake
gem_source: /path/to/gems/rake-1.0.gem
state: present
"""
import re
from ansible.module_utils.basic import AnsibleModule
def get_rubygems_path(module):
if module.params["executable"]:
result = module.params["executable"].split(" ")
else:
result = [module.get_bin_path("gem", True)]
return result
def get_rubygems_version(module):
if hasattr(get_rubygems_version, "ver"):
return get_rubygems_version.ver
cmd = get_rubygems_path(module) + ["--version"]
(rc, out, err) = module.run_command(cmd, check_rc=True)
match = re.match(r"^(\d+)\.(\d+)\.(\d+)", out)
if not match:
return None
ver = tuple(int(x) for x in match.groups())
get_rubygems_version.ver = ver
return ver
def get_rubygems_environ(module):
if module.params["install_dir"]:
return {"GEM_HOME": module.params["install_dir"]}
return None
def get_installed_versions(module, remote=False):
cmd = get_rubygems_path(module)
cmd.append("list")
cmd.extend(common_opts(module))
if remote:
cmd.append("--remote")
if module.params["repository"]:
cmd.extend(["--source", module.params["repository"]])
cmd.append(f"^{module.params['name']}$")
environ = get_rubygems_environ(module)
(rc, out, err) = module.run_command(cmd, environ_update=environ, check_rc=True)
installed_versions = []
for line in out.splitlines():
match = re.match(r"\S+\s+\((?:default: )?(.+)\)", line)
if match:
versions = match.group(1)
for version in versions.split(", "):
installed_versions.append(version.split()[0])
return installed_versions
def exists(module):
if module.params["state"] == "latest":
remoteversions = get_installed_versions(module, remote=True)
if remoteversions:
module.params["version"] = remoteversions[0]
installed_versions = get_installed_versions(module)
if module.params["version"]:
if module.params["version"] in installed_versions:
return True
else:
if installed_versions:
return True
return False
def common_opts(module):
opts = []
ver = get_rubygems_version(module)
if module.params["norc"] and ver and ver >= (2, 5, 2):
opts.append("--norc")
return opts
def uninstall(module):
if module.check_mode:
return
cmd = get_rubygems_path(module)
environ = get_rubygems_environ(module)
cmd.append("uninstall")
cmd.extend(common_opts(module))
if module.params["install_dir"]:
cmd.extend(["--install-dir", module.params["install_dir"]])
if module.params["bindir"]:
cmd.extend(["--bindir", module.params["bindir"]])
if module.params["version"]:
cmd.extend(["--version", module.params["version"]])
else:
cmd.append("--all")
cmd.append("--executable")
if module.params["force"]:
cmd.append("--force")
cmd.append(module.params["name"])
return module.run_command(cmd, environ_update=environ, check_rc=True)
def install(module):
if module.check_mode:
return
ver = get_rubygems_version(module)
cmd = get_rubygems_path(module)
cmd.append("install")
cmd.extend(common_opts(module))
if module.params["version"]:
cmd.extend(["--version", module.params["version"]])
if module.params["repository"]:
cmd.extend(["--source", module.params["repository"]])
if not module.params["include_dependencies"]:
cmd.append("--ignore-dependencies")
else:
if ver and ver < (2, 0, 0):
cmd.append("--include-dependencies")
if module.params["user_install"]:
cmd.append("--user-install")
else:
cmd.append("--no-user-install")
if module.params["install_dir"]:
cmd.extend(["--install-dir", module.params["install_dir"]])
if module.params["bindir"]:
cmd.extend(["--bindir", module.params["bindir"]])
if module.params["pre_release"]:
cmd.append("--pre")
if not module.params["include_doc"]:
if ver and ver < (2, 0, 0):
cmd.append("--no-rdoc")
cmd.append("--no-ri")
else:
cmd.append("--no-document")
if module.params["env_shebang"]:
cmd.append("--env-shebang")
cmd.append(module.params["gem_source"])
if module.params["build_flags"]:
cmd.extend(["--", module.params["build_flags"]])
if module.params["force"]:
cmd.append("--force")
module.run_command(cmd, check_rc=True)
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type="path"),
gem_source=dict(type="path"),
include_dependencies=dict(default=True, type="bool"),
name=dict(required=True, type="str"),
repository=dict(aliases=["source"], type="str"),
state=dict(default="present", choices=["present", "absent", "latest"], type="str"),
user_install=dict(default=True, type="bool"),
install_dir=dict(type="path"),
bindir=dict(type="path"),
norc=dict(type="bool", default=True),
pre_release=dict(default=False, type="bool"),
include_doc=dict(default=False, type="bool"),
env_shebang=dict(default=False, type="bool"),
version=dict(type="str"),
build_flags=dict(type="str"),
force=dict(default=False, type="bool"),
),
supports_check_mode=True,
mutually_exclusive=[["gem_source", "repository"], ["gem_source", "version"]],
)
if module.params["version"] and module.params["state"] == "latest":
module.fail_json(msg="Cannot specify version when state=latest")
if module.params["gem_source"] and module.params["state"] == "latest":
module.fail_json(msg="Cannot maintain state=latest when installing from local source")
if module.params["user_install"] and module.params["install_dir"]:
module.fail_json(msg="install_dir requires user_install=false")
if not module.params["gem_source"]:
module.params["gem_source"] = module.params["name"]
changed = False
if module.params["state"] in ["present", "latest"]:
if not exists(module):
install(module)
changed = True
elif module.params["state"] == "absent":
if exists(module):
command_output = uninstall(module)
if command_output is not None and exists(module):
rc, out, err = command_output
module.fail_json(
msg=(
f"Failed to uninstall gem '{module.params['name']}': it is still present after 'gem uninstall'. "
"This usually happens with default or system gems provided by the OS, "
"which cannot be removed with the gem command."
),
rc=rc,
stdout=out,
stderr=err,
)
else:
changed = True
result = {}
result["name"] = module.params["name"]
result["state"] = module.params["state"]
if module.params["version"]:
result["version"] = module.params["version"]
result["changed"] = changed
module.exit_json(**result)
if __name__ == "__main__":
main()