From 6cb388a98af1939139ee26645f47741b4b02dfac Mon Sep 17 00:00:00 2001 From: Adam Miller Date: Thu, 14 Dec 2017 12:47:47 -0600 Subject: [PATCH] New Module: package_facts (#33195) * new package_facts module Signed-off-by: Adam Miller * remove package_facts pkg manager aliases, they are misleading Signed-off-by: Adam Miller * fix package_facts tests now that aliases are dropped Signed-off-by: Adam Miller --- .../modules/packaging/os/package_facts.py | 237 ++++++++++++++++++ .../integration/targets/package_facts/aliases | 3 + .../targets/package_facts/tasks/main.yml | 75 ++++++ 3 files changed, 315 insertions(+) create mode 100644 lib/ansible/modules/packaging/os/package_facts.py create mode 100644 test/integration/targets/package_facts/aliases create mode 100644 test/integration/targets/package_facts/tasks/main.yml diff --git a/lib/ansible/modules/packaging/os/package_facts.py b/lib/ansible/modules/packaging/os/package_facts.py new file mode 100644 index 00000000000..d97c8a0b18e --- /dev/null +++ b/lib/ansible/modules/packaging/os/package_facts.py @@ -0,0 +1,237 @@ +#!/usr/bin/python +# (c) 2017, Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# most of it copied from AWX's scan_packages module + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +ANSIBLE_METADATA = {'metadata_version': '1.1', + 'status': ['preview'], + 'supported_by': 'community'} + +DOCUMENTATION = ''' +module: package_facts +short_description: package information as facts +description: + - Return information about installed packages as facts +options: + manager: + description: + - The package manager used by the system so we can query the package information + default: auto + choices: ["auto", "rpm", "apt"] + required: False +version_added: "2.5" +author: + - Matthew Jones + - Brian Coca + - Adam Miller +''' + +EXAMPLES = ''' +- name: get the rpm package facts + package_facts: + manager: "auto" + +- name: show them + debug: var=ansible_facts.packages + +''' + +RETURN = ''' +ansible_facts: + description: facts to add to ansible_facts + returned: always + type: complex + contains: + packages: + description: list of dicts with package information + returned: when operating system level package manager is specified or auto detected manager + type: dict + sample_rpm: + { + "packages": { + "kernel": [ + { + "arch": "x86_64", + "epoch": null, + "name": "kernel", + "release": "514.26.2.el7", + "source": "rpm", + "version": "3.10.0" + }, + { + "arch": "x86_64", + "epoch": null, + "name": "kernel", + "release": "514.16.1.el7", + "source": "rpm", + "version": "3.10.0" + }, + { + "arch": "x86_64", + "epoch": null, + "name": "kernel", + "release": "514.10.2.el7", + "source": "rpm", + "version": "3.10.0" + }, + { + "arch": "x86_64", + "epoch": null, + "name": "kernel", + "release": "514.21.1.el7", + "source": "rpm", + "version": "3.10.0" + }, + { + "arch": "x86_64", + "epoch": null, + "name": "kernel", + "release": "693.2.2.el7", + "source": "rpm", + "version": "3.10.0" + } + ], + "kernel-tools": [ + { + "arch": "x86_64", + "epoch": null, + "name": "kernel-tools", + "release": "693.2.2.el7", + "source": "rpm", + "version": "3.10.0" + } + ], + "kernel-tools-libs": [ + { + "arch": "x86_64", + "epoch": null, + "name": "kernel-tools-libs", + "release": "693.2.2.el7", + "source": "rpm", + "version": "3.10.0" + } + ], + } + } + sample_deb: + { + "packages": { + "libbz2-1.0": [ + { + "version": "1.0.6-5", + "source": "apt", + "arch": "amd64", + "name": "libbz2-1.0" + } + ], + "patch": [ + { + "version": "2.7.1-4ubuntu1", + "source": "apt", + "arch": "amd64", + "name": "patch" + } + ], + } + } +''' + +import sys + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + + +def rpm_package_list(): + + try: + import rpm + except ImportError: + module.fail_json('Unable to use the rpm python bindings, please ensure they are installed under the python the module runs under') + + trans_set = rpm.TransactionSet() + installed_packages = {} + for package in trans_set.dbMatch(): + package_details = dict(name=package[rpm.RPMTAG_NAME], + version=package[rpm.RPMTAG_VERSION], + release=package[rpm.RPMTAG_RELEASE], + epoch=package[rpm.RPMTAG_EPOCH], + arch=package[rpm.RPMTAG_ARCH], + source='rpm') + if package_details['name'] not in installed_packages: + installed_packages[package_details['name']] = [package_details] + else: + installed_packages[package_details['name']].append(package_details) + return installed_packages + + +def apt_package_list(): + + try: + import apt + except ImportError: + module.fail_json('Unable to use the apt python bindings, please ensure they are installed under the python the module runs under') + + apt_cache = apt.Cache() + installed_packages = {} + apt_installed_packages = [pk for pk in apt_cache.keys() if apt_cache[pk].is_installed] + for package in apt_installed_packages: + ac_pkg = apt_cache[package].installed + package_details = dict(name=package, version=ac_pkg.version, arch=ac_pkg.architecture, source='apt') + if package_details['name'] not in installed_packages: + installed_packages[package_details['name']] = [package_details] + else: + installed_packages[package_details['name']].append(package_details) + return installed_packages + + +# FIXME: add more listing methods +def main(): + global module + module = AnsibleModule(argument_spec=dict(manager=dict())) + manager = module.params['manager'] + packages = {} + results = {} + + if manager is None or manager == 'auto': + + # detect! + for manager_lib in ('rpm', 'apt'): + try: + dummy = __import__(manager_lib) + manager = manager_lib + break + except ImportError: + pass + + # FIXME: add more detection methods + try: + if manager == "rpm": + packages = rpm_package_list() + elif manager == "apt": + packages = apt_package_list() + else: + if manager: + results['msg'] = 'Unsupported package manager: %s' % manager + results['skipped'] = True + else: + module.fail_json(msg='Could not detect supported package manager') + except Exception as e: + from traceback import format_tb + module.fail_json(msg='Failed to retrieve packages: %s' % to_text(e), exception=format_tb(sys.exc_info()[2])) + + results['ansible_facts'] = {} + # Set the facts, this will override the facts in ansible_facts that might + # exist from previous runs when using operating system level or distribution + # package managers + results['ansible_facts']['packages'] = packages + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/test/integration/targets/package_facts/aliases b/test/integration/targets/package_facts/aliases new file mode 100644 index 00000000000..a553e79913f --- /dev/null +++ b/test/integration/targets/package_facts/aliases @@ -0,0 +1,3 @@ +posix/ci/group3 +skip/freebsd +skip/osx diff --git a/test/integration/targets/package_facts/tasks/main.yml b/test/integration/targets/package_facts/tasks/main.yml new file mode 100644 index 00000000000..8dec1649953 --- /dev/null +++ b/test/integration/targets/package_facts/tasks/main.yml @@ -0,0 +1,75 @@ +# Test playbook for the package_facts module +# (c) 2017, Adam Miller + +# This file is part of Ansible +# +# Ansible 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. +# +# Ansible 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 Ansible. If not, see . + +- name: Prep package_fact tests - Debian Family + block: + - name: install python apt bindings - python2 + package: name="python-apt" state=installed + when: ansible_python.version.major|int == 2 + + - name: install python apt bindings - python3 + package: name="python3-apt" state=installed + when: ansible_python.version.major|int == 3 + + - name: Gather package facts + package_facts: + manager: apt + + - name: check for ansible_facts.packages exists + assert: + that: ansible_facts.packages is defined + when: ansible_os_family == "Debian" + +- name: Run package_fact tests - Red Hat Family + block: + - name: Gather package facts + package_facts: + manager: rpm + + - name: check for ansible_facts.packages exists + assert: + that: ansible_facts.packages is defined + when: (ansible_os_family == "RedHat") + +- name: Run package_fact tests - SUSE/OpenSUSE Family + block: + - name: install python rpm bindings - python2 + package: name="rpm-python" state=installed + when: ansible_python.version.major|int == 2 + + - name: install python rpm bindings - python3 + package: name="python3-rpm" state=installed + when: ansible_python.version.major|int == 3 + + - name: Gather package facts + package_facts: + manager: rpm + + - name: check for ansible_facts.packages exists + assert: + that: ansible_facts.packages is defined + when: (ansible_os_family == "openSUSE Leap") or (ansible_os_family == "Suse") + +# Check that auto detection works also +- name: Gather package facts + package_facts: + manager: auto + +- name: check for ansible_facts.packages exists + assert: + that: ansible_facts.packages is defined