diff --git a/library/packaging/pkgng b/library/packaging/pkgng index 7b0468a7cbd..5bf8fb650f0 100644 --- a/library/packaging/pkgng +++ b/library/packaging/pkgng @@ -46,10 +46,21 @@ options: choices: [ 'yes', 'no' ] required: false default: no + annotation: + description: + - a comma-separated list of keyvalue-pairs of the form + <+/-/:>[=]. A '+' denotes adding an annotation, a + '-' denotes removing an annotation, and ':' denotes modifying an + annotation. + If setting or modifying annotations, a value must be provided. + required: false pkgsite: description: - - specify packagesite to use for downloading packages, if - not specified, use settings from /usr/local/etc/pkg.conf + - for pkgng versions before 1.1.4, specify packagesite to use + for downloading packages, if not specified, use settings from + /usr/local/etc/pkg.conf + for newer pkgng versions, specify a the name of a repository + configured in /usr/local/etc/pkg/repos required: false author: bleader notes: @@ -60,6 +71,9 @@ EXAMPLES = ''' # Install package foo - pkgng: name=foo state=present +# Annotate package foo and bar +- pkgng: name=foo,bar annotation=+test1=baz,-test2,:test3=foobar + # Remove packages foo and bar - pkgng: name=foo,bar state=absent ''' @@ -68,92 +82,215 @@ EXAMPLES = ''' import json import shlex import os +import re import sys -def query_package(module, pkgin_path, name): +def query_package(module, pkgng_path, name): - rc, out, err = module.run_command("%s info -g -e %s" % (pkgin_path, name)) + rc, out, err = module.run_command("%s info -g -e %s" % (pkgng_path, name)) if rc == 0: return True return False +def pkgng_older_than(module, pkgng_path, compare_version): + + rc, out, err = module.run_command("%s -v" % pkgng_path) + version = map(lambda x: int(x), re.split(r'[\._]', out)) -def remove_packages(module, pkgin_path, packages): + i = 0 + new_pkgng = True + while compare_version[i] == version[i]: + i += 1 + if i == min(len(compare_version), len(version)): + break + else: + if compare_version[i] > version[i]: + new_pkgng = False + return not new_pkgng + + +def remove_packages(module, pkgng_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): + if not query_package(module, pkgng_path, package): continue if not module.check_mode: - rc, out, err = module.run_command("%s delete -y %s" % (pkgin_path, package)) + rc, out, err = module.run_command("%s delete -y %s" % (pkgng_path, package)) - if not module.check_mode and query_package(module, pkgin_path, package): + if not module.check_mode and query_package(module, pkgng_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) + return (True, "removed %s package(s)" % remove_c) - module.exit_json(changed=False, msg="package(s) already absent") + return (False, "package(s) already absent") -def install_packages(module, pkgin_path, packages, cached, pkgsite): +def install_packages(module, pkgng_path, packages, cached, pkgsite): install_c = 0 - if pkgsite != "": - pkgsite="PACKAGESITE=%s" % (pkgsite) + # as of pkg-1.1.4, PACKAGESITE is deprecated in favor of repository definitions + # in /usr/local/etc/pkg/repos + old_pkgng = pkgng_older_than(module, pkgng_path, [1, 1, 4]) + + if old_pkgng and (pkgsite != ""): + pkgsite = "PACKAGESITE=%s" % (pkgsite) if not module.check_mode and cached == "no": - rc, out, err = module.run_command("%s %s update" % (pkgsite, pkgin_path)) + if old_pkgng: + rc, out, err = module.run_command("%s %s update" % (pkgsite, pkgng_path)) + else: + rc, out, err = module.run_command("%s update" % (pkgng_path)) if rc != 0: module.fail_json(msg="Could not update catalogue") for package in packages: - if query_package(module, pkgin_path, package): + if query_package(module, pkgng_path, package): continue if not module.check_mode: - rc, out, err = module.run_command("%s %s install -g -U -y %s" % (pkgsite, pkgin_path, package)) + if old_pkgng: + rc, out, err = module.run_command("%s %s install -g -U -y %s" % (pkgsite, pkgng_path, package)) + else: + rc, out, err = module.run_command("%s install -r %s -g -U -y %s" % (pkgng_path, pkgsite, package)) - if not module.check_mode and not query_package(module, pkgin_path, package): + if not module.check_mode and not query_package(module, pkgng_path, package): module.fail_json(msg="failed to install %s: %s" % (package, out), stderr=err) install_c += 1 if install_c > 0: - module.exit_json(changed=True, msg="present %s package(s)" % (install_c)) + return (True, "added %s package(s)" % (install_c)) - module.exit_json(changed=False, msg="package(s) already present") + return (False, "package(s) already present") +def annotation_query(module, pkgng_path, package, tag): + rc, out, err = module.run_command("%s info -g -A %s" % (pkgng_path, package)) + match = re.search(r'^\s*(?P%s)\s*:\s*(?P\w+)' % tag, out, flags=re.MULTILINE) + if match: + return match.group('value') + return False + + +def annotation_add(module, pkgng_path, package, tag, value): + _value = annotation_query(module, pkgng_path, package, tag) + if not _value: + # Annotation does not exist, add it. + rc, out, err = module.run_command('%s annotate -y -A %s %s "%s"' + % (pkgng_path, package, tag, value)) + if rc != 0: + module.fail_json("could not annotate %s: %s" + % (package, out), stderr=err) + return True + elif _value != value: + # Annotation exists, but value differs + module.fail_json( + mgs="failed to annotate %s, because %s is already set to %s, but should be set to %s" + % (package, tag, _value, value)) + return False + else: + # Annotation exists, nothing to do + return False + +def annotation_delete(module, pkgng_path, package, tag, value): + _value = annotation_query(module, pkgng_path, package, tag) + if _value: + rc, out, err = module.run_command('%s annotate -y -D %s %s' + % (pkgng_path, package, tag)) + if rc != 0: + module.fail_json("could not delete annotation to %s: %s" + % (package, out), stderr=err) + return True + return False + +def annotation_modify(module, pkgng_path, package, tag, value): + _value = annotation_query(module, pkgng_path, package, tag) + if not value: + # No such tag + module.fail_json("could not change annotation to %s: tag %s does not exist" + % (package, tag)) + elif _value == value: + # No change in value + return False + else: + rc,out,err = module.run_command('%s annotate -y -M %s %s "%s"' + % (pkgng_path, package, tag, value)) + if rc != 0: + module.fail_json("could not change annotation annotation to %s: %s" + % (package, out), stderr=err) + return True + + +def annotate_packages(module, pkgng_path, packages, annotation): + annotate_c = 0 + annotations = map(lambda _annotation: + re.match(r'(?P[\+-:])(?P\w+)(=(?P\w+))?', + _annotation).groupdict(), + re.split(r',', annotation)) + + operation = { + '+': annotation_add, + '-': annotation_delete, + ':': annotation_modify + } + + for package in packages: + for _annotation in annotations: + annotate_c += ( 1 if operation[_annotation['operation']]( + module, pkgng_path, package, + _annotation['tag'], _annotation['value']) else 0 ) + + if annotate_c > 0: + return (True, "added %s annotations." % annotate_c) + return (False, "changed no annotations") def main(): module = AnsibleModule( argument_spec = dict( - state = dict(default="present", choices=["present","absent"]), + state = dict(default="present", choices=["present","absent"], required=False), name = dict(aliases=["pkg"], required=True), cached = dict(default=False, type='bool'), + annotation = dict(default="", required=False), pkgsite = dict(default="", required=False)), supports_check_mode = True) - pkgin_path = module.get_bin_path('pkg', True) + pkgng_path = module.get_bin_path('pkg', True) p = module.params pkgs = p["name"].split(",") + changed = False + msgs = [] + if p["state"] == "present": - install_packages(module, pkgin_path, pkgs, p["cached"], p["pkgsite"]) + _changed, _msg = install_packages(module, pkgng_path, pkgs, p["cached"], p["pkgsite"]) + changed = changed or _changed + msgs.append(_msg) elif p["state"] == "absent": - remove_packages(module, pkgin_path, pkgs) + _changed, _msg = remove_packages(module, pkgng_path, pkgs) + changed = changed or _changed + msgs.append(_msg) + + if p["annotation"]: + _changed, _msg = annotate_packages(module, pkgng_path, pkgs, p["annotation"]) + changed = changed or _changed + msgs.append(_msg) + + module.exit_json(changed=changed, msg=", ".join(msgs)) + + # import module snippets from ansible.module_utils.basic import *