From ef0f8520414950bd706026e23e419c8d8a1faad9 Mon Sep 17 00:00:00 2001 From: David Wittman Date: Fri, 13 Mar 2015 21:09:33 -0500 Subject: [PATCH] Fix multiple issues with alternatives module - Changes are no longer erroneously reported on RHEL (#12) - Adding new link groups on Debian works again. - This was broken in a previous commit by assuming the OS was RHEL if `update-alternatives --query ` had a return code of 2 - Prefer `--display` over `--query` for determining available alternatives - --display is more distro-agnostic and simplifies the code - Fix missing `msg=` in `fail_json` call when `link` is missing - Document that `link` is required on RHEL-based distros Tested on Ubuntu 12.04+ and CentOS 6/7 --- .../modules/extras/system/alternatives.py | 105 +++++++----------- 1 file changed, 43 insertions(+), 62 deletions(-) diff --git a/lib/ansible/modules/extras/system/alternatives.py b/lib/ansible/modules/extras/system/alternatives.py index 871a494e87..ff4de59cf1 100755 --- a/lib/ansible/modules/extras/system/alternatives.py +++ b/lib/ansible/modules/extras/system/alternatives.py @@ -4,6 +4,7 @@ """ Ansible module to manage symbolic link alternatives. (c) 2014, Gabe Mulley +(c) 2015, David Wittman This file is part of Ansible @@ -26,7 +27,7 @@ DOCUMENTATION = ''' module: alternatives short_description: Manages alternative programs for common commands description: - - Manages symbolic links using the 'update-alternatives' tool provided on debian-like systems. + - Manages symbolic links using the 'update-alternatives' tool - Useful when multiple programs are installed but provide similar functionality (e.g. different editors). version_added: "1.6" options: @@ -41,6 +42,7 @@ options: link: description: - The path to the symbolic link that should point to the real executable. + - This option is required on RHEL-based distributions required: false requirements: [ update-alternatives ] ''' @@ -55,12 +57,14 @@ EXAMPLES = ''' DEFAULT_LINK_PRIORITY = 50 +import re + def main(): module = AnsibleModule( argument_spec = dict( name = dict(required=True), - path = dict(required=True), + path = dict(required=True), link = dict(required=False), ), supports_check_mode=True, @@ -71,78 +75,55 @@ def main(): path = params['path'] link = params['link'] - UPDATE_ALTERNATIVES = module.get_bin_path('update-alternatives',True) + UPDATE_ALTERNATIVES = module.get_bin_path('update-alternatives',True) current_path = None all_alternatives = [] - os_family = None - (rc, query_output, query_error) = module.run_command( - [UPDATE_ALTERNATIVES, '--query', name] + # Run `update-alternatives --display ` to find existing alternatives + (rc, display_output, _) = module.run_command( + [UPDATE_ALTERNATIVES, '--display', name] ) - # Gather the current setting and all alternatives from the query output. - # Query output should look something like this on Debian systems: - - # Name: java - # Link: /usr/bin/java - # Slaves: - # java.1.gz /usr/share/man/man1/java.1.gz - # Status: manual - # Best: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java - # Value: /usr/lib/jvm/java-6-openjdk-amd64/jre/bin/java - - # Alternative: /usr/lib/jvm/java-6-openjdk-amd64/jre/bin/java - # Priority: 1061 - # Slaves: - # java.1.gz /usr/lib/jvm/java-6-openjdk-amd64/jre/man/man1/java.1.gz - - # Alternative: /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java - # Priority: 1071 - # Slaves: - # java.1.gz /usr/lib/jvm/java-7-openjdk-amd64/jre/man/man1/java.1.gz - if rc == 0: - os_family = "Debian" - for line in query_output.splitlines(): - split_line = line.split(':') - if len(split_line) == 2: - key = split_line[0] - value = split_line[1].strip() - if key == 'Value': - current_path = value - elif key == 'Alternative': - all_alternatives.append(value) - elif key == 'Link' and not link: - link = value - elif rc == 2: - os_family = "RedHat" - # This is the version of update-alternatives that is shipped with - # chkconfig on RedHat-based systems. Try again with the right options. - (rc, query_output, query_error) = module.run_command( - [UPDATE_ALTERNATIVES, '--list'] - ) - for line in query_output.splitlines(): - line_name, line_mode, line_path = line.strip().split("\t") - if line_name != name: - continue - current_path = line_path - break + # Alternatives already exist for this link group + # Parse the output to determine the current path of the symlink and + # available alternatives + current_path_regex = re.compile(r'^\s*link currently points to (.*)$', + re.MULTILINE) + alternative_regex = re.compile(r'^(\/.*)\s-\spriority', re.MULTILINE) + + current_path = current_path_regex.search(display_output).group(1) + all_alternatives = alternative_regex.findall(display_output) + + if not link: + # Read the current symlink target from `update-alternatives --query` + # in case we need to install the new alternative before setting it. + # + # This is only compatible on Debian-based systems, as the other + # alternatives don't have --query available + rc, query_output, _ = module.run_command( + [UPDATE_ALTERNATIVES, '--query', name] + ) + if rc == 0: + for line in query_output.splitlines(): + if line.startswith('Link:'): + link = line.split()[1] + break if current_path != path: if module.check_mode: module.exit_json(changed=True, current_path=current_path) try: # install the requested path if necessary - # (unsupported on the RedHat version) - if path not in all_alternatives and os_family == "Debian": - if link: - module.run_command( - [UPDATE_ALTERNATIVES, '--install', link, name, path, str(DEFAULT_LINK_PRIORITY)], - check_rc=True - ) - else: - module.fail_json("Needed to install the alternative, but unable to do so, as we are missking the link") + if path not in all_alternatives: + if not link: + module.fail_json(msg="Needed to install the alternative, but unable to do so as we are missing the link") + + module.run_command( + [UPDATE_ALTERNATIVES, '--install', link, name, path, str(DEFAULT_LINK_PRIORITY)], + check_rc=True + ) # select the requested path module.run_command( @@ -151,7 +132,7 @@ def main(): ) module.exit_json(changed=True) - except subprocess.CalledProcessError, cpe: + except subprocess.CalledProcessError as cpe: module.fail_json(msg=str(dir(cpe))) else: module.exit_json(changed=False)