mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-06-04 15:29:10 -07:00
Unflatmap community.general (#5461)
* Move files. * Update imports and references. * Move wrongly placed files. * Reverse redirects, deprecate long → short name redirects. * Simplify contribution guidelines for new modules. * Rewrite BOTMETA. * Add changelog fragment. * Fix ignore.txt files.
This commit is contained in:
parent
2b0bebc8fc
commit
b531ecdc9b
1033 changed files with 4802 additions and 1989 deletions
398
plugins/modules/alternatives.py
Normal file
398
plugins/modules/alternatives.py
Normal file
|
@ -0,0 +1,398 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2014, Gabe Mulley <gabe.mulley@gmail.com>
|
||||
# Copyright (c) 2015, David Wittman <dwittman@gmail.com>
|
||||
# Copyright (c) 2022, Marius Rieder <marius.rieder@scs.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 absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
---
|
||||
module: alternatives
|
||||
short_description: Manages alternative programs for common commands
|
||||
description:
|
||||
- Manages symbolic links using the 'update-alternatives' tool.
|
||||
- Useful when multiple programs are installed but provide similar functionality (e.g. different editors).
|
||||
author:
|
||||
- Marius Rieder (@jiuka)
|
||||
- David Wittman (@DavidWittman)
|
||||
- Gabe Mulley (@mulby)
|
||||
options:
|
||||
name:
|
||||
description:
|
||||
- The generic name of the link.
|
||||
type: str
|
||||
required: true
|
||||
path:
|
||||
description:
|
||||
- The path to the real executable that the link should point to.
|
||||
type: path
|
||||
required: true
|
||||
link:
|
||||
description:
|
||||
- The path to the symbolic link that should point to the real executable.
|
||||
- This option is always required on RHEL-based distributions. On Debian-based distributions this option is
|
||||
required when the alternative I(name) is unknown to the system.
|
||||
type: path
|
||||
priority:
|
||||
description:
|
||||
- The priority of the alternative. If no priority is given for creation C(50) is used as a fallback.
|
||||
type: int
|
||||
state:
|
||||
description:
|
||||
- C(present) - install the alternative (if not already installed), but do
|
||||
not set it as the currently selected alternative for the group.
|
||||
- C(selected) - install the alternative (if not already installed), and
|
||||
set it as the currently selected alternative for the group.
|
||||
- C(auto) - install the alternative (if not already installed), and
|
||||
set the group to auto mode. Added in community.general 5.1.0.
|
||||
- C(absent) - removes the alternative. Added in community.general 5.1.0.
|
||||
choices: [ present, selected, auto, absent ]
|
||||
default: selected
|
||||
type: str
|
||||
version_added: 4.8.0
|
||||
subcommands:
|
||||
description:
|
||||
- A list of subcommands.
|
||||
- Each subcommand needs a name, a link and a path parameter.
|
||||
type: list
|
||||
elements: dict
|
||||
aliases: ['slaves']
|
||||
suboptions:
|
||||
name:
|
||||
description:
|
||||
- The generic name of the subcommand.
|
||||
type: str
|
||||
required: true
|
||||
path:
|
||||
description:
|
||||
- The path to the real executable that the subcommand should point to.
|
||||
type: path
|
||||
required: true
|
||||
link:
|
||||
description:
|
||||
- The path to the symbolic link that should point to the real subcommand executable.
|
||||
type: path
|
||||
required: true
|
||||
version_added: 5.1.0
|
||||
requirements: [ update-alternatives ]
|
||||
'''
|
||||
|
||||
EXAMPLES = r'''
|
||||
- name: Correct java version selected
|
||||
community.general.alternatives:
|
||||
name: java
|
||||
path: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
|
||||
|
||||
- name: Alternatives link created
|
||||
community.general.alternatives:
|
||||
name: hadoop-conf
|
||||
link: /etc/hadoop/conf
|
||||
path: /etc/hadoop/conf.ansible
|
||||
|
||||
- name: Make java 32 bit an alternative with low priority
|
||||
community.general.alternatives:
|
||||
name: java
|
||||
path: /usr/lib/jvm/java-7-openjdk-i386/jre/bin/java
|
||||
priority: -10
|
||||
|
||||
- name: Install Python 3.5 but do not select it
|
||||
community.general.alternatives:
|
||||
name: python
|
||||
path: /usr/bin/python3.5
|
||||
link: /usr/bin/python
|
||||
state: present
|
||||
|
||||
- name: Install Python 3.5 and reset selection to auto
|
||||
community.general.alternatives:
|
||||
name: python
|
||||
path: /usr/bin/python3.5
|
||||
link: /usr/bin/python
|
||||
state: auto
|
||||
|
||||
- name: keytool is a subcommand of java
|
||||
community.general.alternatives:
|
||||
name: java
|
||||
link: /usr/bin/java
|
||||
path: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java
|
||||
subcommands:
|
||||
- name: keytool
|
||||
link: /usr/bin/keytool
|
||||
path: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/keytool
|
||||
'''
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
||||
|
||||
class AlternativeState:
|
||||
PRESENT = "present"
|
||||
SELECTED = "selected"
|
||||
ABSENT = "absent"
|
||||
AUTO = "auto"
|
||||
|
||||
@classmethod
|
||||
def to_list(cls):
|
||||
return [cls.PRESENT, cls.SELECTED, cls.ABSENT, cls.AUTO]
|
||||
|
||||
|
||||
class AlternativesModule(object):
|
||||
_UPDATE_ALTERNATIVES = None
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
self.result = dict(changed=False, diff=dict(before=dict(), after=dict()))
|
||||
self.module.run_command_environ_update = {'LC_ALL': 'C'}
|
||||
self.messages = []
|
||||
self.run()
|
||||
|
||||
@property
|
||||
def mode_present(self):
|
||||
return self.module.params.get('state') in [AlternativeState.PRESENT, AlternativeState.SELECTED, AlternativeState.AUTO]
|
||||
|
||||
@property
|
||||
def mode_selected(self):
|
||||
return self.module.params.get('state') == AlternativeState.SELECTED
|
||||
|
||||
@property
|
||||
def mode_auto(self):
|
||||
return self.module.params.get('state') == AlternativeState.AUTO
|
||||
|
||||
def run(self):
|
||||
self.parse()
|
||||
|
||||
if self.mode_present:
|
||||
# Check if we need to (re)install
|
||||
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.install()
|
||||
|
||||
# Check if we need to set the preference
|
||||
if self.mode_selected and self.current_path != self.path:
|
||||
self.set()
|
||||
|
||||
# Check if we need to reset to auto
|
||||
if self.mode_auto and self.current_mode == 'manual':
|
||||
self.auto()
|
||||
else:
|
||||
# Check if we need to uninstall
|
||||
if self.path in self.current_alternatives:
|
||||
self.remove()
|
||||
|
||||
self.result['msg'] = ' '.join(self.messages)
|
||||
self.module.exit_json(**self.result)
|
||||
|
||||
def install(self):
|
||||
if not os.path.exists(self.path):
|
||||
self.module.fail_json(msg="Specified path %s does not exist" % self.path)
|
||||
if not self.link:
|
||||
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.module.params['subcommands'] is not None:
|
||||
subcommands = [['--slave', subcmd['link'], subcmd['name'], subcmd['path']] for subcmd in self.subcommands]
|
||||
cmd += [item for sublist in subcommands for item in sublist]
|
||||
|
||||
self.result['changed'] = True
|
||||
self.messages.append("Install alternative '%s' for '%s'." % (self.path, self.name))
|
||||
|
||||
if not self.module.check_mode:
|
||||
self.module.run_command(cmd, check_rc=True)
|
||||
|
||||
if self.module._diff:
|
||||
self.result['diff']['after'] = dict(
|
||||
state=AlternativeState.PRESENT,
|
||||
path=self.path,
|
||||
priority=self.priority,
|
||||
link=self.link,
|
||||
)
|
||||
if self.subcommands:
|
||||
self.result['diff']['after'].update(dict(
|
||||
subcommands=self.subcommands
|
||||
))
|
||||
|
||||
def remove(self):
|
||||
cmd = [self.UPDATE_ALTERNATIVES, '--remove', self.name, self.path]
|
||||
self.result['changed'] = True
|
||||
self.messages.append("Remove alternative '%s' from '%s'." % (self.path, self.name))
|
||||
|
||||
if not self.module.check_mode:
|
||||
self.module.run_command(cmd, check_rc=True)
|
||||
|
||||
if self.module._diff:
|
||||
self.result['diff']['after'] = dict(state=AlternativeState.ABSENT)
|
||||
|
||||
def set(self):
|
||||
cmd = [self.UPDATE_ALTERNATIVES, '--set', self.name, self.path]
|
||||
self.result['changed'] = True
|
||||
self.messages.append("Set alternative '%s' for '%s'." % (self.path, self.name))
|
||||
|
||||
if not self.module.check_mode:
|
||||
self.module.run_command(cmd, check_rc=True)
|
||||
|
||||
if self.module._diff:
|
||||
self.result['diff']['after']['state'] = AlternativeState.SELECTED
|
||||
|
||||
def auto(self):
|
||||
cmd = [self.UPDATE_ALTERNATIVES, '--auto', self.name]
|
||||
self.messages.append("Set alternative to auto for '%s'." % (self.name))
|
||||
self.result['changed'] = True
|
||||
|
||||
if not self.module.check_mode:
|
||||
self.module.run_command(cmd, check_rc=True)
|
||||
|
||||
if self.module._diff:
|
||||
self.result['diff']['after']['state'] = AlternativeState.PRESENT
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.module.params.get('name')
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self.module.params.get('path')
|
||||
|
||||
@property
|
||||
def link(self):
|
||||
return self.module.params.get('link') or self.current_link
|
||||
|
||||
@property
|
||||
def priority(self):
|
||||
if self.module.params.get('priority') is not None:
|
||||
return self.module.params.get('priority')
|
||||
return self.current_alternatives.get(self.path, {}).get('priority', 50)
|
||||
|
||||
@property
|
||||
def subcommands(self):
|
||||
if self.module.params.get('subcommands') is not None:
|
||||
return self.module.params.get('subcommands')
|
||||
elif self.path in self.current_alternatives and self.current_alternatives[self.path].get('subcommands'):
|
||||
return self.current_alternatives[self.path].get('subcommands')
|
||||
return None
|
||||
|
||||
@property
|
||||
def UPDATE_ALTERNATIVES(self):
|
||||
if self._UPDATE_ALTERNATIVES is None:
|
||||
self._UPDATE_ALTERNATIVES = self.module.get_bin_path('update-alternatives', True)
|
||||
return self._UPDATE_ALTERNATIVES
|
||||
|
||||
def parse(self):
|
||||
self.current_mode = None
|
||||
self.current_path = None
|
||||
self.current_link = None
|
||||
self.current_alternatives = {}
|
||||
|
||||
# Run `update-alternatives --display <name>` to find existing alternatives
|
||||
(rc, display_output, dummy) = self.module.run_command(
|
||||
[self.UPDATE_ALTERNATIVES, '--display', self.name]
|
||||
)
|
||||
|
||||
if rc != 0:
|
||||
self.module.debug("No current alternative found. '%s' exited with %s" % (self.UPDATE_ALTERNATIVES, rc))
|
||||
return
|
||||
|
||||
current_mode_regex = re.compile(r'\s-\s(?:status\sis\s)?(\w*)(?:\smode|.)$', re.MULTILINE)
|
||||
current_path_regex = re.compile(r'^\s*link currently points to (.*)$', re.MULTILINE)
|
||||
current_link_regex = re.compile(r'^\s*link \w+ is (.*)$', re.MULTILINE)
|
||||
subcmd_path_link_regex = re.compile(r'^\s*slave (\S+) is (.*)$', re.MULTILINE)
|
||||
|
||||
alternative_regex = re.compile(r'^(\/.*)\s-\s(?:family\s\S+\s)?priority\s(\d+)((?:\s+slave.*)*)', re.MULTILINE)
|
||||
subcmd_regex = re.compile(r'^\s+slave (.*): (.*)$', re.MULTILINE)
|
||||
|
||||
match = current_mode_regex.search(display_output)
|
||||
if not match:
|
||||
self.module.debug("No current mode found in output")
|
||||
return
|
||||
self.current_mode = match.group(1)
|
||||
|
||||
match = current_path_regex.search(display_output)
|
||||
if not match:
|
||||
self.module.debug("No current path found in output")
|
||||
else:
|
||||
self.current_path = match.group(1)
|
||||
|
||||
match = current_link_regex.search(display_output)
|
||||
if not match:
|
||||
self.module.debug("No current link found in output")
|
||||
else:
|
||||
self.current_link = match.group(1)
|
||||
|
||||
subcmd_path_map = dict(subcmd_path_link_regex.findall(display_output))
|
||||
if not subcmd_path_map and self.subcommands:
|
||||
subcmd_path_map = dict((s['name'], s['link']) for s in self.subcommands)
|
||||
|
||||
for path, prio, subcmd in alternative_regex.findall(display_output):
|
||||
self.current_alternatives[path] = dict(
|
||||
priority=int(prio),
|
||||
subcommands=[dict(
|
||||
name=name,
|
||||
path=spath,
|
||||
link=subcmd_path_map.get(name)
|
||||
) for name, spath in subcmd_regex.findall(subcmd) if spath != '(null)']
|
||||
)
|
||||
|
||||
if self.module._diff:
|
||||
if self.path in self.current_alternatives:
|
||||
self.result['diff']['before'].update(dict(
|
||||
state=AlternativeState.PRESENT,
|
||||
path=self.path,
|
||||
priority=self.current_alternatives[self.path].get('priority'),
|
||||
link=self.current_link,
|
||||
))
|
||||
if self.current_alternatives[self.path].get('subcommands'):
|
||||
self.result['diff']['before'].update(dict(
|
||||
subcommands=self.current_alternatives[self.path].get('subcommands')
|
||||
))
|
||||
if self.current_mode == 'manual' and self.current_path != self.path:
|
||||
self.result['diff']['before'].update(dict(
|
||||
state=AlternativeState.SELECTED
|
||||
))
|
||||
else:
|
||||
self.result['diff']['before'].update(dict(
|
||||
state=AlternativeState.ABSENT
|
||||
))
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
module = AnsibleModule(
|
||||
argument_spec=dict(
|
||||
name=dict(type='str', required=True),
|
||||
path=dict(type='path', required=True),
|
||||
link=dict(type='path'),
|
||||
priority=dict(type='int'),
|
||||
state=dict(
|
||||
type='str',
|
||||
choices=AlternativeState.to_list(),
|
||||
default=AlternativeState.SELECTED,
|
||||
),
|
||||
subcommands=dict(type='list', elements='dict', aliases=['slaves'], options=dict(
|
||||
name=dict(type='str', required=True),
|
||||
path=dict(type='path', required=True),
|
||||
link=dict(type='path', required=True),
|
||||
)),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
||||
AlternativesModule(module)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
Loading…
Add table
Add a link
Reference in a new issue