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
parent
737717d015
commit
523439ab62
|
@ -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).
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
Loading…
Reference in New Issue