Files
Alexei Znamensky f5da5c9681 gem - fix --user-install conflict with OS-injected --install-dir (#11873)
* gem - fix --user-install conflict with OS-injected --install-dir

Some distributions (e.g. Fedora) inject --install-dir via operating_system.rb
as a platform default. Combining that with --user-install causes a gem CLI
parser error. Resolve the user install directory at install time and pass
--install-dir instead, which is semantically equivalent and avoids the conflict.
Uninstall is intentionally left unscoped so gem can find gems regardless of
where they were originally installed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* gem - add changelog fragment for #11873

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* gem - fix user_install handling for install and uninstall

Two issues found in CI:

1. `gem environment user_gemhome` is not supported on older RubyGems (e.g.
   Ubuntu 20.04 ships 3.1.2). Simplify get_user_install_dir() to always parse
   the full `gem environment` output for "USER INSTALLATION DIRECTORY", which
   is stable across all supported versions.

2. On Fedora, `gem uninstall` without flags only searches the system gem path
   (set by operating_system.rb), so it cannot find gems installed to the user
   dir via --install-dir. Add user_install to the uninstall args_order so that
   gem uninstall --user-install is passed when user_install=True. The OS
   defaults conflict only applies to gem install, not gem uninstall.
   The integration test is updated to be consistent: the user_install:false
   install/remove block now also specifies user_install:false on removal.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* gem - use --install-dir for both install and uninstall of user gems

gem uninstall --user-install does not reliably find gems on Fedora/RHEL when
running as root, because those systems may disable user gem home for root and
Gem.user_dir may differ from the path resolved via 'gem environment'.

Use --install-dir <user_dir> for uninstall as well, since that is the exact
path used during install, making the operation consistent across platforms.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* gem - add override_platform_install_dir option and type hints

- add type hints to all functions
- fix misleading comment about --install-dir scoping for uninstall
- add override_platform_install_dir option (default=false) to opt in to
  resolving and passing the user gem dir explicitly to both gem install
  and gem uninstall, working around OS-injected platform defaults on
  distributions such as Fedora
- reclassify changelog fragment as minor_changes (new parameter, not
  backport-eligible)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(gem): add integration test for override_platform_install_dir

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(gem): skip default user_install test on RedHat family

OS-injected --install-dir on RHEL/Fedora makes the default user_install: true
case fail. The override_platform_install_dir block already covers the correct
path on those platforms.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 07:28:56 +12:00

269 lines
7.8 KiB
YAML

---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
# test code for the gem module
# Copyright (c) 2014, James Tanner <tanner.jc@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
- when:
- not (ansible_facts.os_family == 'Alpine') # TODO
block:
- include_vars: '{{ item }}'
with_first_found:
- files:
- '{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_major_version }}.yml'
- '{{ ansible_facts.distribution }}-{{ ansible_facts.distribution_version }}.yml'
- '{{ ansible_facts.distribution }}.yml'
- '{{ ansible_facts.os_family }}.yml'
- 'default.yml'
paths: '../vars'
- name: Install dependencies for test
package:
name: "{{ item }}"
state: present
loop: "{{ test_packages }}"
when: ansible_facts.distribution != "MacOSX"
# default user_install: skip on RedHat/Fedora where OS-injected --install-dir conflicts
- when: ansible_facts.os_family != "RedHat"
block:
- name: Install a gem (default user_install)
gem:
name: gist
state: present
register: install_gem_result
- name: List gems
command: gem list
register: current_gems
- name: Ensure gem was installed
assert:
that:
- install_gem_result is changed
- current_gems.stdout is search('gist\s+\([0-9.]+\)')
- name: Remove a gem
gem:
name: gist
state: absent
register: remove_gem_results
- name: List gems
command: gem list
register: current_gems
- name: Verify gem is not installed
assert:
that:
- remove_gem_results is changed
- current_gems.stdout is not search('gist\s+\([0-9.]+\)')
# install gem in --no-user-install
- block:
- name: Install a gem with --no-user-install
gem:
name: gist
state: present
user_install: false
register: install_gem_result
- name: List gems
command: gem list
register: current_gems
- name: Ensure gem was installed
assert:
that:
- install_gem_result is changed
- current_gems.stdout is search('gist\s+\([0-9.]+\)')
- name: Remove a gem
gem:
name: gist
state: absent
user_install: false
register: remove_gem_results
- name: List gems
command: gem list
register: current_gems
- name: Verify gem is not installed
assert:
that:
- remove_gem_results is changed
- current_gems.stdout is not search('gist\s+\([0-9.]+\)')
when: ansible_facts.user_uid == 0
# Check custom gem directory
- name: Install gem in a custom directory with incorrect options
gem:
name: gist
state: present
install_dir: "{{ remote_tmp_dir }}/gems"
ignore_errors: true
register: install_gem_fail_result
- debug:
var: install_gem_fail_result
tags: debug
- name: Ensure previous task failed
assert:
that:
- install_gem_fail_result is failed
- install_gem_fail_result.msg == 'install_dir requires user_install=false'
- name: Install a gem in a custom directory
gem:
name: gist
state: present
user_install: false
install_dir: "{{ remote_tmp_dir }}/gems"
register: install_gem_result
- name: Find gems in custom directory
find:
paths: "{{ remote_tmp_dir }}/gems/gems"
file_type: directory
contains: gist
register: gem_search
- name: Ensure gem was installed in custom directory
assert:
that:
- install_gem_result is changed
- gem_search.files[0].path is search('gist-[0-9.]+')
ignore_errors: true
- name: Remove a gem in a custom directory
gem:
name: gist
state: absent
user_install: false
install_dir: "{{ remote_tmp_dir }}/gems"
register: install_gem_result
- name: Find gems in custom directory
find:
paths: "{{ remote_tmp_dir }}/gems/gems"
file_type: directory
contains: gist
register: gem_search
- name: Ensure gem was removed in custom directory
assert:
that:
- install_gem_result is changed
- gem_search.files | length == 0
# Custom directory for executables (--bindir)
- name: Install gem with custom bindir
gem:
name: gist
state: present
bindir: "{{ remote_tmp_dir }}/custom_bindir"
norc: true
user_install: false
register: install_gem_result
- name: Get stats of gem executable
stat:
path: "{{ remote_tmp_dir }}/custom_bindir/gist"
register: gem_bindir_stat
- name: Ensure gem executable was installed in custom directory
assert:
that:
- install_gem_result is changed
- gem_bindir_stat.stat.exists and gem_bindir_stat.stat.isreg
- name: Remove gem with custom bindir
gem:
name: gist
state: absent
bindir: "{{ remote_tmp_dir }}/custom_bindir"
norc: true
user_install: false
register: install_gem_result
- name: Get stats of gem executable
stat:
path: "{{ remote_tmp_dir }}/custom_bindir/gist"
register: gem_bindir_stat
- name: Ensure gem executable was removed from custom directory
assert:
that:
- install_gem_result is changed
- not gem_bindir_stat.stat.exists
# override_platform_install_dir: install and remove using explicit user gem dir
- name: Install a gem with override_platform_install_dir
gem:
name: gist
state: present
override_platform_install_dir: true
register: install_gem_result
- name: List gems
command: gem list
register: current_gems
- name: Ensure gem was installed with override_platform_install_dir
assert:
that:
- install_gem_result is changed
- current_gems.stdout is search('gist\s+\([0-9.]+\)')
- name: Install a gem with override_platform_install_dir (idempotency)
gem:
name: gist
state: present
override_platform_install_dir: true
register: install_gem_result
- name: Ensure install is idempotent
assert:
that:
- install_gem_result is not changed
- name: Remove a gem with override_platform_install_dir
gem:
name: gist
state: absent
override_platform_install_dir: true
register: remove_gem_result
- name: List gems
command: gem list
register: current_gems
- name: Verify gem was removed with override_platform_install_dir
assert:
that:
- remove_gem_result is changed
- current_gems.stdout is not search('gist\s+\([0-9.]+\)')
- name: Attempt to uninstall default gem 'json'
community.general.gem:
name: json
state: absent
when: ansible_facts.distribution == "Ubuntu"
register: gem_result
ignore_errors: true
- name: Assert gem uninstall failed as expected
when: ansible_facts.distribution == "Ubuntu"
assert:
that:
- gem_result is failed
- gem_result.msg.startswith("Failed to uninstall gem 'json'")