From 85c1440edea9d2cefdd85c686770df6ea523c060 Mon Sep 17 00:00:00 2001 From: Jasper Lievisse Adriaanse Date: Wed, 13 Apr 2016 11:02:42 +0200 Subject: [PATCH] Tweak and extend the pkgin module - make path to pkgin a global and stop passing it around; it's not going to change while ansible is running - add support for several new options: * upgrade * full_upgrade * force * clean - allow for update_cache to be run in the same task as upgrading/installing packages instead of needing a separate task for that --- packaging/os/pkgin.py | 158 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 131 insertions(+), 27 deletions(-) diff --git a/packaging/os/pkgin.py b/packaging/os/pkgin.py index 5277f218242..cd48385eefe 100755 --- a/packaging/os/pkgin.py +++ b/packaging/os/pkgin.py @@ -3,6 +3,7 @@ # Copyright (c) 2013 Shaun Zinck # Copyright (c) 2015 Lawrence Leonard Gilbert +# Copyright (c) 2016 Jasper Lievisse Adriaanse # # Written by Shaun Zinck # Based on pacman module written by Afterburn @@ -33,6 +34,7 @@ version_added: "1.0" author: - "Larry Gilbert (L2G)" - "Shaun Zinck (@szinck)" + - "Jasper Lievisse Adriaanse (@jasperla)" 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 @@ -57,6 +59,34 @@ options: default: no choices: [ "yes", "no" ] version_added: "2.1" + upgrade: + description: + - Upgrade main packages to their newer versions + required: false + default: no + choices: [ "yes", "no" ] + version_added: "2.1" + full_upgrade: + description: + - Upgrade all packages to their newer versions + required: false + default: no + choices: [ "yes", "no" ] + version_added: "2.1" + clean: + description: + - Clean packages cache + required: false + default: no + choices: [ "yes", "no" ] + version_added: "2.1" + force: + description: + - Force package reinstall + required: false + default: no + choices: [ "yes", "no" ] + version_added: "2.1" ''' EXAMPLES = ''' @@ -74,12 +104,24 @@ EXAMPLES = ''' # Update repositories as a separate step - pkgin: update_cache=yes + +# Upgrade main packages (equivalent to C(pkgin upgrade)) +- pkgin: upgrade=yes + +# Upgrade all packages (equivalent to C(pkgin full-upgrade)) +- pkgin: full_upgrade=yes + +# Force-upgrade all packages (equivalent to C(pkgin -F full-upgrade)) +- pkgin: full_upgrade=yes force=yes + +# clean packages cache (equivalent to C(pkgin clean)) +- pkgin: clean=yes ''' import re -def query_package(module, pkgin_path, name): +def query_package(module, name): """Search for the package by name. Possible return values: @@ -89,7 +131,7 @@ def query_package(module, pkgin_path, name): """ # test whether '-p' (parsable) flag is supported. - rc, out, err = module.run_command("%s -p -v" % pkgin_path) + rc, out, err = module.run_command("%s -p -v" % PKGIN_PATH) if rc == 0: pflag = '-p' @@ -100,7 +142,7 @@ def query_package(module, pkgin_path, name): # Use "pkgin search" to find the package. The regular expression will # only match on the complete name. - rc, out, err = module.run_command("%s %s search \"^%s$\"" % (pkgin_path, pflag, name)) + rc, out, err = module.run_command("%s %s search \"^%s$\"" % (PKGIN_PATH, pflag, name)) # rc will not be 0 unless the search was a success if rc == 0: @@ -162,37 +204,43 @@ def format_action_message(module, action, count): return message + "s" -def format_pkgin_command(module, pkgin_path, command, package=None): +def format_pkgin_command(module, command, package=None): # Not all commands take a package argument, so cover this up by passing # an empty string. Some commands (e.g. 'update') will ignore extra # arguments, however this behaviour cannot be relied on for others. if package is None: package = "" - vars = { "pkgin": pkgin_path, + if module.params["force"]: + force = "-F" + else: + force = "" + + vars = { "pkgin": PKGIN_PATH, "command": command, - "package": package } + "package": package, + "force": force} if module.check_mode: return "%(pkgin)s -n %(command)s %(package)s" % vars else: - return "%(pkgin)s -y %(command)s %(package)s" % vars + return "%(pkgin)s -y %(force)s %(command)s %(package)s" % vars -def remove_packages(module, pkgin_path, packages): +def remove_packages(module, 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): + if not query_package(module, package): continue rc, out, err = module.run_command( - format_pkgin_command(module, pkgin_path, "remove", package)) + format_pkgin_command(module, "remove", package)) - if not module.check_mode and query_package(module, pkgin_path, package): + if not module.check_mode and query_package(module, package): module.fail_json(msg="failed to remove %s: %s" % (package, out)) remove_c += 1 @@ -203,18 +251,18 @@ def remove_packages(module, pkgin_path, packages): module.exit_json(changed=False, msg="package(s) already absent") -def install_packages(module, pkgin_path, packages): +def install_packages(module, packages): install_c = 0 for package in packages: - if query_package(module, pkgin_path, package): + if query_package(module, package): continue rc, out, err = module.run_command( - format_pkgin_command(module, pkgin_path, "install", package)) + format_pkgin_command(module, "install", package)) - if not module.check_mode and not query_package(module, pkgin_path, package): + if not module.check_mode and not query_package(module, package): module.fail_json(msg="failed to install %s: %s" % (package, out)) install_c += 1 @@ -224,41 +272,97 @@ def install_packages(module, pkgin_path, packages): module.exit_json(changed=False, msg="package(s) already present") -def update_package_db(module, pkgin_path): +def update_package_db(module): rc, out, err = module.run_command( - format_pkgin_command(module, pkgin_path, "update")) + format_pkgin_command(module, "update")) if rc == 0: - return True + if re.search('database for.*is up-to-date\n$', out): + return False, "datebase is up-to-date" + else: + return True, "updated repository database" else: module.fail_json(msg="could not update package db") +def do_upgrade_packages(module, full=False): + if full: + cmd = "full-upgrade" + else: + cmd = "upgrade" + + rc, out, err = module.run_command( + format_pkgin_command(module, cmd)) + + if rc == 0: + if re.search('^nothing to do.\n$', out): + module.exit_json(changed=False, msg="nothing left to upgrade") + else: + module.fail_json(msg="could not %s packages" % cmd) + +def upgrade_packages(module): + do_upgrade_packages(module) + +def full_upgrade_packages(module): + do_upgrade_packages(module, True) + +def clean_cache(module): + rc, out, err = module.run_command( + format_pkgin_command(module, "clean")) + + if rc == 0: + # There's no indication if 'clean' actually removed anything, + # so assume it did. + module.exit_json(changed=True, msg="cleaned caches") + else: + module.fail_json(msg="could not clean package cache") def main(): module = AnsibleModule( argument_spec = dict( state = dict(default="present", choices=["present","absent"]), name = dict(aliases=["pkg"], type='list'), - update_cache = dict(default='no', type='bool')), - required_one_of = [['name', 'update_cache']], + update_cache = dict(default='no', type='bool'), + upgrade = dict(default='no', type='bool'), + full_upgrade = dict(default='no', type='bool'), + clean = dict(default='no', type='bool'), + force = dict(default='no', type='bool')), + required_one_of = [['name', 'update_cache', 'upgrade', 'full_upgrade', 'clean']], supports_check_mode = True) - pkgin_path = module.get_bin_path('pkgin', True, ['/opt/local/bin']) + global PKGIN_PATH + PKGIN_PATH = module.get_bin_path('pkgin', True, ['/opt/local/bin']) - p = module.params + module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C') - pkgs = p["name"] + p = module.params if p["update_cache"]: - update_package_db(module, pkgin_path) + c, msg = update_package_db(module) + if not (p['name'] or p["upgrade"] or p["full_upgrade"]): + module.exit_json(changed=c, msg=msg) + + if p["upgrade"]: + upgrade_packages(module) if not p['name']: - module.exit_json(changed=True, msg='updated repository database') + module.exit_json(changed=True, msg='upgraded packages') + + if p["full_upgrade"]: + full_upgrade_packages(module) + if not p['name']: + module.exit_json(changed=True, msg='upgraded all packages') + + if p["clean"]: + clean_cache(module) + if not p['name']: + module.exit_json(changed=True, msg='cleaned caches') + + pkgs = p["name"] if p["state"] == "present": - install_packages(module, pkgin_path, pkgs) + install_packages(module, pkgs) elif p["state"] == "absent": - remove_packages(module, pkgin_path, pkgs) + remove_packages(module, pkgs) # import module snippets from ansible.module_utils.basic import *