#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright: (c) 2019, Marcin Krol # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type ANSIBLE_METADATA = {'metadata_version': '1.0', 'status': ['preview'], 'supported_by': 'community'} DOCUMENTATION = ''' --- module: poldek short_description: Manage packages with I(poldek) description: - Manage packages with the I(poldek) package manager, which is used by TLD Linux and PLD Linux version_added: "1.0" author: - Marcin Krol options: name: description: - Name or list of names of the package(s) or file(s) to install, upgrade, or remove. Can't be used in combination with C(upgrade). aliases: [ package, pkg ] state: description: - Desired state of the package. default: present choices: [ absent, latest, present ] extra_args: description: - Additional option to pass to poldek when enforcing C(state). default: update_cache: description: - Whether or not to refresh the master package lists. - This can be run as part of a package installation or as a separate step. default: no type: bool aliases: [ update-cache ] update_cache_extra_args: description: - Additional option to pass to poldek when enforcing C(update_cache). default: upgrade: description: - Whether or not to upgrade the whole system. Can't be used in combination with C(name). default: no type: bool upgrade_extra_args: description: - Additional option to pass to poldek when enforcing C(upgrade). default: ''' RETURN = ''' packages: description: a list of packages that have been changed returned: when upgrade is set to yes type: list sample: [ package, other-package ] ''' EXAMPLES = ''' - name: Install packages foo and bar poldek: name: - foo - bar state: present - name: Update package cache and upgrade package foo poldek: name: foo state: latest update_cache: yes - name: Remove packages foo and bar poldek: name: - foo - bar state: absent - name: Update package cache only poldek: update_cache: yes - name: Update package cache and upgrade all packages poldek: upgrade: yes update_cache: yes ''' import re from ansible.module_utils.basic import AnsibleModule def query_package(module, poldek_path, name): """Query the package status in both the local system and the repository. Returns a boolean to indicate if the package is installed, a second boolean to indicate if the package is up-to-date. Note: indexes must be up to date to ensure that poldek knows latest version of package. """ lcmd = "%s -q --shcmd='ls -n --installed %s'" % (poldek_path, name) lrc, lstdout, lstderr = module.run_command(lcmd, check_rc=False) rcmd = "%s -q --shcmd='ls -n --installed --upgradeable %s'" % (poldek_path, name) rrc, rstdout, rstderr = module.run_command(rcmd, check_rc=False) if lrc != 0 or rrc != 0: return False, False pkg_latest = False if rstdout == "": return True, True else: return True, False def update_package_db(module, poldek_path): if module.params['force']: module.params["update_cache_extra_args"] += " --upa" cmd = "%s --noask --up %s" % (poldek_path, module.params["update_cache_extra_args"]) rc, stdout, stderr = module.run_command(cmd, check_rc=False) if rc == 0: return True else: module.fail_json(msg="error updating package database") def upgrade(module, poldek_path): cmdupgrade = "%s -v --noask --upgrade-dist %s" % (poldek_path, module.params["upgrade_extra_args"]) cmdupgradeable = "%s -q --shcmd='ls --upgradeable --qf=\"%%{NAME}-%%{VERSION}-%%{RELEASE}\n\"' %s" % (poldek_path, module.params["upgrade_extra_args"]) rc, stdout, stderr = module.run_command(cmdupgradeable, check_rc=False) packages = stdout.split('\n')[:-1] if stdout == "": module.exit_json(changed=False, msg='Nothing to upgrade', packages=packages, stdout=stdout, stderr=stderr, rc=rc) else: if module.check_mode: module.exit_json(changed=True, msg="%s package(s) would be upgraded" % (len(packages)), packages=packages, stdout=stdout, stderr=stderr, rc=rc) rc, stdout, stderr = module.run_command(cmdupgrade, check_rc=False) if rc == 0: module.exit_json(changed=True, msg="%s package(s) upgraded" % (len(packages)), packages=packages, stdout=stdout, stderr=stderr, rc=rc) else: module.fail_json(msg="Error while upgrading packages", stdout=stdout, stderr=stderr, rc=rc) def remove_packages(module, poldek_path, packages): if module.params["force"]: module.params["extra_args"] += " --force --nodeps" remove_c = 0 out = "" err = "" for package in packages: installed, updated = query_package(module, poldek_path, package) if not installed: continue cmd = "%s -v --noask --erase %s %s" % (poldek_path, module.params["extra_args"], package) rc, stdout, stderr = module.run_command(cmd, check_rc=False) out = out + stdout err = err + stderr if rc != 0: module.fail_json(msg="failed to remove %s" % (package), stdout=out, stderr=err, rc=rc) remove_c += 1 if remove_c > 0: module.exit_json(changed=True, msg="removed %s package(s)" % (remove_c), packages=packages, stdout=out, stderr=err, rc=0) module.exit_json(changed=False, msg="package(s) already absent", packages=packages, stdout=out, stderr=err, rc=0) def install_packages(module, poldek_path, state, packages): if module.params["force"]: module.params["extra_args"] += " --force --nodeps" install_c = 0 out = "" err = "" for package in packages: installed, updated = query_package(module, poldek_path, package) if installed and (state == 'present' or (state == 'latest' and updated)): continue cmd = "%s -v --noask --upgrade %s %s" % (poldek_path, module.params["extra_args"], package) rc, stdout, stderr = module.run_command(cmd, check_rc=False) out = out + stdout err = err + stderr if rc != 0: module.fail_json(msg="failed to install %s" % (package), stdout=out, stderr=err, rc=rc) install_c += 1 if install_c > 0: module.exit_json(changed=True, msg="installed %s package(s)" % (install_c), packages=packages, stdout=out, stderr=err, rc=0) module.exit_json(changed=False, msg="package(s) already installed", packages=packages, stdout=out, stderr=err, rc=0) def check_packages(module, poldek_path, packages, state): would_be_changed = [] for package in packages: installed, updated = query_package(module, poldek_path, package) if ((state in ["present", "latest"] and not installed) or (state == "absent" and installed) or (state == "latest" and not updated)): would_be_changed.append(package) if state == "absent": state = "removed" if state == "present" or state == "latest": state = "installed" if would_be_changed: module.exit_json(changed=True, msg="%s package(s) would be %s" % ( len(would_be_changed), state)) else: module.exit_json(changed=False, msg="package(s) already %s" % state) def main(): module = AnsibleModule( argument_spec=dict( name=dict(type='list', aliases=['pkg', 'package']), state=dict(type='str', default='present', choices=['present', 'installed', 'latest', 'absent', 'removed']), extra_args=dict(type='str', default=''), upgrade=dict(type='bool', default=False), upgrade_extra_args=dict(type='str', default=''), update_cache=dict(type='bool', default=False, aliases=['update-cache']), update_cache_extra_args=dict(type='str', default=''), force=dict(type='bool', default=False), ), required_one_of=[['name', 'update_cache', 'upgrade']], mutually_exclusive=[['name', 'upgrade']], supports_check_mode=True, ) poldek_path = module.get_bin_path('poldek', True) p = module.params if p['state'] in ['present', 'installed']: p['state'] = 'present' elif p['state'] in ['absent', 'removed']: p['state'] = 'absent' if p["update_cache"] and not module.check_mode: update_package_db(module, poldek_path) if not (p['name'] or p['upgrade']): module.exit_json(changed=True, msg='Updated package database') if p['update_cache'] and module.check_mode and not (p['name'] or p['upgrade']): module.exit_json(changed=True, msg='Would have updated package database') if p['upgrade']: upgrade(module, poldek_path) if p['name']: pkgs = p['name'] if module.check_mode: check_packages(module, poldek_path, pkgs, p['state']) if p['state'] in ['present', 'latest']: install_packages(module, poldek_path, p['state'], pkgs) elif p['state'] == 'absent': remove_packages(module, poldek_path, pkgs) else: module.exit_json(changed=False, msg="No package specified to work on") if __name__ == "__main__": main()