diff --git a/packaging/os/apk.py b/packaging/os/apk.py new file mode 100644 index 00000000000..f14d5593443 --- /dev/null +++ b/packaging/os/apk.py @@ -0,0 +1,213 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# (c) 2015, Kevin Brebanov +# Based on pacman (Afterburn , Aaron Bull Schaefer ) +# and apt (Matthew Williams >) modules. +# +# This module is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This software is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this software. If not, see . + +DOCUMENTATION = ''' +--- +module: apk +short_description: Manages apk packages +description: + - Manages I(apk) packages for Alpine Linux. +options: + name: + description: + - A package name, like C(foo), or mutliple packages, like C(foo, bar). + required: false + default: null + state: + description: + - Indicates the desired package(s) state. + - C(present) ensures the package(s) is/are present. + - C(absent) ensures the package(s) is/are absent. + - C(latest) ensures the package(s) is/are present and the latest version(s). + required: false + default: present + choices: [ "present", "absent", "latest" ] + update_cache: + description: + - Update repository indexes. Can be run with other steps or on it's own. + required: false + default: no + choices: [ "yes", "no" ] + upgrade: + description: + - Upgrade all installed packages to their latest version. + required: false + default: no + choices: [ "yes", "no" ] +''' + +EXAMPLES = ''' +# Update repositories and install "foo" package +- apk: name=foo update_cache=yes + +# Update repositories and install "foo" and "bar" packages +- apk: name=foo,bar update_cache=yes + +# Remove "foo" package +- apk: name=foo state=absent + +# Remove "foo" and "bar" packages +- apk: name=foo,bar state=absent + +# Install the package "foo" +- apk: name=foo state=present + +# Install the packages "foo" and "bar" +- apk: name=foo,bar state=present + +# Update repositories and update package "foo" to latest version +- apk: name=foo state=latest update_cache=yes + +# Update repositories and update packages "foo" and "bar" to latest versions +- apk: name=foo,bar state=latest update_cache=yes + +# Update all installed packages to the latest versions +- apk: upgrade=yes + +# Update repositories as a separate step +- apk: update_cache=yes +''' + +import os +import re + +def update_package_db(module): + cmd = "%s update" % (APK_PATH) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) + if rc == 0: + return True + else: + module.fail_json(msg="could not update package db") + +def query_package(module, name): + cmd = "%s -v info --installed %s" % (APK_PATH, name) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) + if rc == 0: + return True + else: + return False + +def query_latest(module, name): + cmd = "%s version %s" % (APK_PATH, name) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) + search_pattern = "(%s)-[\d\.\w]+-[\d\w]+\s+(.)\s+[\d\.\w]+-[\d\w]+\s+" % (name) + match = re.search(search_pattern, stdout) + if match and match.group(2) == "<": + return False + return True + +def upgrade_packages(module): + if module.check_mode: + cmd = "%s upgrade --simulate" % (APK_PATH) + else: + cmd = "%s upgrade" % (APK_PATH) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) + if rc != 0: + module.fail_json(msg="failed to upgrade packages") + if re.search('^OK', stdout): + module.exit_json(changed=False, msg="packages already upgraded") + module.exit_json(changed=True, msg="upgraded packages") + +def install_packages(module, names, state): + upgrade = False + uninstalled = [] + for name in names: + if not query_package(module, name): + uninstalled.append(name) + elif state == 'latest' and not query_latest(module, name): + upgrade = True + if not uninstalled and not upgrade: + module.exit_json(changed=False, msg="package(s) already installed") + names = " ".join(uninstalled) + if upgrade: + if module.check_mode: + cmd = "%s add --upgrade --simulate %s" % (APK_PATH, names) + else: + cmd = "%s add --upgrade %s" % (APK_PATH, names) + else: + if module.check_mode: + cmd = "%s add --simulate %s" % (APK_PATH, names) + else: + cmd = "%s add %s" % (APK_PATH, names) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) + if rc != 0: + module.fail_json(msg="failed to install %s" % (names)) + module.exit_json(changed=True, msg="installed %s package(s)" % (names)) + +def remove_packages(module, names): + installed = [] + for name in names: + if query_package(module, name): + installed.append(name) + if not installed: + module.exit_json(changed=False, msg="package(s) already removed") + names = " ".join(installed) + if module.check_mode: + cmd = "%s del --purge --simulate %s" % (APK_PATH, names) + else: + cmd = "%s del --purge %s" % (APK_PATH, names) + rc, stdout, stderr = module.run_command(cmd, check_rc=False) + if rc != 0: + module.fail_json(msg="failed to remove %s package(s)" % (names)) + module.exit_json(changed=True, msg="removed %s package(s)" % (names)) + +# ========================================== +# Main control flow. + +def main(): + module = AnsibleModule( + argument_spec = dict( + state = dict(default='present', choices=['present', 'installed', 'absent', 'removed', 'latest']), + name = dict(type='list'), + update_cache = dict(default='no', choices=BOOLEANS, type='bool'), + upgrade = dict(default='no', choices=BOOLEANS, type='bool'), + ), + required_one_of = [['name', 'update_cache', 'upgrade']], + supports_check_mode = True + ) + + global APK_PATH + APK_PATH = module.get_bin_path('apk', required=True) + + p = module.params + + # normalize the state parameter + if p['state'] in ['present', 'installed']: + p['state'] = 'present' + if p['state'] in ['absent', 'removed']: + p['state'] = 'absent' + + if p['update_cache']: + update_package_db(module) + if not p['name']: + module.exit_json(changed=True, msg='updated repository indexes') + + if p['upgrade']: + upgrade_packages(module) + + if p['state'] in ['present', 'latest']: + install_packages(module, p['name'], p['state']) + elif p['state'] == 'absent': + remove_packages(module, p['name']) + +# Import module snippets. +from ansible.module_utils.basic import * +if __name__ == '__main__': + main()