diff --git a/lib/ansible/modules/extras/packaging/os/pkgin.py b/lib/ansible/modules/extras/packaging/os/pkgin.py old mode 100755 new mode 100644 index 866c9f76a4c..f4c203e56e0 --- a/lib/ansible/modules/extras/packaging/os/pkgin.py +++ b/lib/ansible/modules/extras/packaging/os/pkgin.py @@ -1,8 +1,10 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -# (c) 2013, Shaun Zinck -# Written by Shaun Zinck +# Copyright (c) 2013 Shaun Zinck +# Copyright (c) 2015 Lawrence Leonard Gilbert +# +# Written by Shaun Zinck # Based on pacman module written by Afterburn # that was based on apt module written by Matthew Williams # @@ -23,27 +25,32 @@ DOCUMENTATION = ''' --- module: pkgin -short_description: Package manager for SmartOS +short_description: Package manager for SmartOS, NetBSD, et al. description: - - Manages SmartOS packages + - "The standard package manager for SmartOS, but also usable on NetBSD + or any OS that uses C(pkgsrc). (Home: U(http://pkgin.net/))" version_added: "1.0" +author: Shaun Zinck, Larry Gilbert +notes: + - "Known bug with pkgin < 0.8.0: if a package is removed and another + package depends on it, the other package will be silently removed as + well. New to Ansible 1.9: check-mode support." options: name: description: - - name of package to install/remove + - Name of package to install/remove; + - multiple names may be given, separated by commas required: true state: description: - - state of the package + - Intended state of the package choices: [ 'present', 'absent' ] required: false default: present -author: Shaun Zinck -notes: [] ''' EXAMPLES = ''' -# install package foo" +# install package foo - pkgin: name=foo state=present # remove package foo @@ -60,64 +67,97 @@ import os import sys import pipes -def query_package(module, pkgin_path, name, state="present"): +def query_package(module, pkgin_path, name): + """Search for the package by name. - if state == "present": + Possible return values: + * "present" - installed, no upgrade needed + * "outdated" - installed, but can be upgraded + * False - not installed or not found + """ - rc, out, err = module.run_command("%s -y list | grep ^%s" % (pipes.quote(pkgin_path), pipes.quote(name)), use_unsafe_shell=True) + # Use "pkgin search" to find the package. The regular expression will + # only match on the complete name. + rc, out, err = module.run_command("%s search \"^%s$\"" % (pkgin_path, name)) - if rc == 0: - # At least one package with a package name that starts with ``name`` - # is installed. For some cases this is not sufficient to determine - # wether the queried package is installed. - # - # E.g. for ``name='gcc47'``, ``gcc47`` not being installed, but - # ``gcc47-libs`` being installed, ``out`` would be: - # - # gcc47-libs-4.7.2nb4 The GNU Compiler Collection (GCC) support shared libraries. - # - # Multiline output is also possible, for example with the same query - # and bot ``gcc47`` and ``gcc47-libs`` being installed: - # - # gcc47-libs-4.7.2nb4 The GNU Compiler Collection (GCC) support shared libraries. - # gcc47-4.7.2nb3 The GNU Compiler Collection (GCC) - 4.7 Release Series + # rc will not be 0 unless the search was a success + if rc == 0: - # Loop over lines in ``out`` - for line in out.split('\n'): + # Get first line + line = out.split('\n')[0] - # Strip description - # (results in sth. like 'gcc47-libs-4.7.2nb4') - pkgname_with_version = out.split(' ')[0] + # Break up line at spaces. The first part will be the package with its + # version (e.g. 'gcc47-libs-4.7.2nb4'), and the second will be the state + # of the package: + # '' - not installed + # '<' - installed but out of date + # '=' - installed and up to date + # '>' - installed but newer than the repository version + pkgname_with_version, raw_state = out.split(' ')[0:2] - # Strip version - # (results in sth like 'gcc47-libs') - pkgname_without_version = '-'.join(pkgname_with_version.split('-')[:-1]) + # Strip version + # (results in sth like 'gcc47-libs') + pkgname_without_version = '-'.join(pkgname_with_version.split('-')[:-1]) - if name == pkgname_without_version: - return True + if name != pkgname_without_version: + return False + # no fall-through - return False + # The package was found; now return its state + if raw_state == '<': + return 'outdated' + elif raw_state == '=' or raw_state == '>': + return 'present' + else: + return False + + +def format_action_message(module, action, count): + vars = { "actioned": action, + "count": count } + + if module.check_mode: + message = "would have %(actioned)s %(count)d package" % vars + else: + message = "%(actioned)s %(count)d package" % vars + + if count == 1: + return message + else: + return message + "s" + + +def format_pkgin_command(module, pkgin_path, command, package): + vars = { "pkgin": pkgin_path, + "command": command, + "package": package } + + if module.check_mode: + return "%(pkgin)s -n %(command)s %(package)s" % vars + else: + return "%(pkgin)s -y %(command)s %(package)s" % vars def remove_packages(module, pkgin_path, packages): remove_c = 0 + # Using a for loop incase of error, we can report the package that failed for package in packages: # Query the package first, to see if we even need to remove if not query_package(module, pkgin_path, package): continue - rc, out, err = module.run_command("%s -y remove %s" % (pkgin_path, package)) + rc, out, err = module.run_command( + format_pkgin_command(module, pkgin_path, "remove", package)) - if query_package(module, pkgin_path, package): + if not module.check_mode and query_package(module, pkgin_path, package): module.fail_json(msg="failed to remove %s: %s" % (package, out)) remove_c += 1 if remove_c > 0: - - module.exit_json(changed=True, msg="removed %s package(s)" % remove_c) + module.exit_json(changed=True, msg=format_action_message(module, "removed", remove_c)) module.exit_json(changed=False, msg="package(s) already absent") @@ -130,15 +170,16 @@ def install_packages(module, pkgin_path, packages): if query_package(module, pkgin_path, package): continue - rc, out, err = module.run_command("%s -y install %s" % (pkgin_path, package)) + rc, out, err = module.run_command( + format_pkgin_command(module, pkgin_path, "install", package)) - if not query_package(module, pkgin_path, package): + if not module.check_mode and not query_package(module, pkgin_path, package): module.fail_json(msg="failed to install %s: %s" % (package, out)) install_c += 1 if install_c > 0: - module.exit_json(changed=True, msg="present %s package(s)" % (install_c)) + module.exit_json(changed=True, msg=format_action_message(module, "installed", install_c)) module.exit_json(changed=False, msg="package(s) already present") @@ -148,7 +189,8 @@ def main(): module = AnsibleModule( argument_spec = dict( state = dict(default="present", choices=["present","absent"]), - name = dict(aliases=["pkg"], required=True))) + name = dict(aliases=["pkg"], required=True)), + supports_check_mode = True) pkgin_path = module.get_bin_path('pkgin', True, ['/opt/local/bin'])