alternatives: add support for "family" parameter (#9096)

* alternatives: added parsing and setting of 'family' for an alternative

* alternatives: added checks for path nullability

* alternatives: added idempotence when setting alternative using family

* alternatives: added family to diff mode

* alternatives: added tests for family

* alternatives: updated documentation and examples

* alternatives: added constraints for 'path' and 'family' parameters.

in any invariants at least one of the parameters must be specified

* alternatives: added changelog fragment

* removed unnecessary check

* added version

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
pull/9143/head
Stanislav Shamilov 2024-11-16 19:34:09 +02:00 committed by GitHub
parent 737717d015
commit 523439ab62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 120 additions and 13 deletions

View File

@ -0,0 +1,2 @@
minor_changes:
- alternatives - add ``family`` parameter that allows to utilize the ``--family`` option available in RedHat version of update-alternatives (https://github.com/ansible-collections/community.general/issues/5060, https://github.com/ansible-collections/community.general/pull/9096).

View File

@ -39,7 +39,11 @@ options:
description:
- The path to the real executable that the link should point to.
type: path
required: true
family:
description:
- The family groups similar alternatives. This option is available only on RHEL-based distributions.
type: str
version_added: 10.1.0
link:
description:
- The path to the symbolic link that should point to the real executable.
@ -98,6 +102,12 @@ EXAMPLES = r'''
name: java
path: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
- name: Select java-11-openjdk.x86_64 family
community.general.alternatives:
name: java
family: java-11-openjdk.x86_64
when: ansible_os_family == 'RedHat'
- name: Alternatives link created
community.general.alternatives:
name: hadoop-conf
@ -182,17 +192,25 @@ class AlternativesModule(object):
subcommands_parameter = self.module.params['subcommands']
priority_parameter = self.module.params['priority']
if (
self.path not in self.current_alternatives or
(priority_parameter is not None and self.current_alternatives[self.path].get('priority') != priority_parameter) or
(subcommands_parameter is not None and (
not all(s in subcommands_parameter for s in self.current_alternatives[self.path].get('subcommands')) or
not all(s in self.current_alternatives[self.path].get('subcommands') for s in subcommands_parameter)
))
self.path is not None and (
self.path not in self.current_alternatives or
(priority_parameter is not None and self.current_alternatives[self.path].get('priority') != priority_parameter) or
(subcommands_parameter is not None and (
not all(s in subcommands_parameter for s in self.current_alternatives[self.path].get('subcommands')) or
not all(s in self.current_alternatives[self.path].get('subcommands') for s in subcommands_parameter)
))
)
):
self.install()
# Check if we need to set the preference
if self.mode_selected and self.current_path != self.path:
is_same_path = self.path is not None and self.current_path == self.path
is_same_family = False
if self.current_path is not None and self.current_path in self.current_alternatives:
current_alternative = self.current_alternatives[self.current_path]
is_same_family = current_alternative.get('family') == self.family
if self.mode_selected and not (is_same_path or is_same_family):
self.set()
# Check if we need to reset to auto
@ -213,6 +231,8 @@ class AlternativesModule(object):
self.module.fail_json(msg='Needed to install the alternative, but unable to do so as we are missing the link')
cmd = [self.UPDATE_ALTERNATIVES, '--install', self.link, self.name, self.path, str(self.priority)]
if self.family is not None:
cmd.extend(["--family", self.family])
if self.module.params['subcommands'] is not None:
subcommands = [['--slave', subcmd['link'], subcmd['name'], subcmd['path']] for subcmd in self.subcommands]
@ -228,6 +248,7 @@ class AlternativesModule(object):
self.result['diff']['after'] = dict(
state=AlternativeState.PRESENT,
path=self.path,
family=self.family,
priority=self.priority,
link=self.link,
)
@ -248,9 +269,15 @@ class AlternativesModule(object):
self.result['diff']['after'] = dict(state=AlternativeState.ABSENT)
def set(self):
cmd = [self.UPDATE_ALTERNATIVES, '--set', self.name, self.path]
# Path takes precedence over family as it is more specific
if self.path is None:
arg = self.family
else:
arg = self.path
cmd = [self.UPDATE_ALTERNATIVES, '--set', self.name, arg]
self.result['changed'] = True
self.messages.append("Set alternative '%s' for '%s'." % (self.path, self.name))
self.messages.append("Set alternative '%s' for '%s'." % (arg, self.name))
if not self.module.check_mode:
self.module.run_command(cmd, check_rc=True)
@ -277,6 +304,10 @@ class AlternativesModule(object):
def path(self):
return self.module.params.get('path')
@property
def family(self):
return self.module.params.get('family')
@property
def link(self):
return self.module.params.get('link') or self.current_link
@ -321,7 +352,7 @@ class AlternativesModule(object):
current_link_regex = re.compile(r'^\s*link \w+ is (.*)$', re.MULTILINE)
subcmd_path_link_regex = re.compile(r'^\s*(?:slave|follower) (\S+) is (.*)$', re.MULTILINE)
alternative_regex = re.compile(r'^(\/.*)\s-\s(?:family\s\S+\s)?priority\s(\d+)((?:\s+(?:slave|follower).*)*)', re.MULTILINE)
alternative_regex = re.compile(r'^(\/.*)\s-\s(?:family\s(\S+)\s)?priority\s(\d+)((?:\s+(?:slave|follower).*)*)', re.MULTILINE)
subcmd_regex = re.compile(r'^\s+(?:slave|follower) (.*): (.*)$', re.MULTILINE)
match = current_mode_regex.search(display_output)
@ -346,9 +377,10 @@ class AlternativesModule(object):
if not subcmd_path_map and self.subcommands:
subcmd_path_map = {s['name']: s['link'] for s in self.subcommands}
for path, prio, subcmd in alternative_regex.findall(display_output):
for path, family, prio, subcmd in alternative_regex.findall(display_output):
self.current_alternatives[path] = dict(
priority=int(prio),
family=family,
subcommands=[dict(
name=name,
path=spath,
@ -383,7 +415,8 @@ def main():
module = AnsibleModule(
argument_spec=dict(
name=dict(type='str', required=True),
path=dict(type='path', required=True),
path=dict(type='path'),
family=dict(type='str'),
link=dict(type='path'),
priority=dict(type='int'),
state=dict(
@ -398,6 +431,7 @@ def main():
)),
),
supports_check_mode=True,
required_one_of=[('path', 'family')]
)
AlternativesModule(module)

View File

@ -58,6 +58,12 @@
- include_tasks: remove_links.yml
- include_tasks: tests_state.yml
# Test for the family parameter
- block:
- include_tasks: remove_links.yml
- include_tasks: tests_family.yml
when: ansible_os_family == 'RedHat'
# Cleanup
always:
- include_tasks: remove_links.yml

View File

@ -0,0 +1,65 @@
---
# Copyright (c) Ansible Project
# 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
- name: Add an alternative with a family
alternatives:
name: dummy
path: /usr/bin/dummy1
link: /usr/bin/dummy
family: family1
priority: 100
state: selected
- name: Ensure that the alternative has family assigned
shell: 'grep family1 {{ alternatives_dir }}/dummy'
- name: Add two alternatives with different families
alternatives:
name: dummy
path: '/usr/bin/dummy{{ item.n }}'
link: /usr/bin/dummy
family: family2
priority: "{{ item.priority }}"
state: present
loop:
- { n: 2, priority: 20 }
- { n: 3, priority: 10 }
- { n: 4, priority: 5 }
# Here we select the whole family of alternatives
- name: Set family as an alternatives
alternatives:
name: dummy
family: family2
state: selected
- name: Ensure manual mode
shell: 'head -n1 {{ alternatives_dir }}/dummy | grep "^manual"'
- name: Execute the current dummy command
shell: dummy
register: cmd
# Despite the fact that there is alternative with higher priority (/usr/bin/dummy1),
# it is not chosen as it doesn't belong to the selected family
- name: Ensure that the alternative from the selected family is used
assert:
that:
- cmd.stdout == "dummy2"
- name: Remove the alternative with the highest priority that belongs to the family
alternatives:
name: dummy
path: '/usr/bin/dummy2'
state: absent
- name: Execute the current dummy command
shell: dummy
register: cmd
- name: Ensure that the next alternative is selected as having the highest priority from the family
assert:
that:
- cmd.stdout == "dummy3"