#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2018, Ansible Project # GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) # SPDX-License-Identifier: GPL-3.0-or-later # started out with AWX's scan_packages module from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = ''' module: pip_package_info short_description: Pip package information description: - Return information about installed pip packages extends_documentation_fragment: - community.general.attributes - community.general.attributes.info_module options: clients: description: - A list of the pip executables that will be used to get the packages. They can be supplied with the full path or just the executable name, for example V(pip3.7). default: ['pip'] required: false type: list elements: path requirements: - pip >= 20.3b1 (necessary for the C(--format) option) - The requested pip executables must be installed on the target. author: - Matthew Jones (@matburt) - Brian Coca (@bcoca) - Adam Miller (@maxamillion) ''' EXAMPLES = ''' - name: Just get the list from default pip community.general.pip_package_info: - name: Get the facts for default pip, pip2 and pip3.6 community.general.pip_package_info: clients: ['pip', 'pip2', 'pip3.6'] - name: Get from specific paths (virtualenvs?) community.general.pip_package_info: clients: '/home/me/projec42/python/pip3.5' ''' RETURN = ''' packages: description: a dictionary of installed package data returned: always type: dict contains: python: description: A dictionary with each pip client which then contains a list of dicts with python package information returned: always type: dict sample: "packages": { "pip": { "Babel": [ { "name": "Babel", "source": "pip", "version": "2.6.0" } ], "Flask": [ { "name": "Flask", "source": "pip", "version": "1.0.2" } ], "Flask-SQLAlchemy": [ { "name": "Flask-SQLAlchemy", "source": "pip", "version": "2.3.2" } ], "Jinja2": [ { "name": "Jinja2", "source": "pip", "version": "2.10" } ], }, } ''' import json import os from ansible.module_utils.common.text.converters import to_text from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.facts.packages import CLIMgr class PIP(CLIMgr): def __init__(self, pip, module): self.CLI = pip self.module = module def list_installed(self): rc, out, err = self.module.run_command([self._cli, 'list', '-l', '--format=json']) if rc != 0: raise Exception("Unable to list packages rc=%s : %s" % (rc, err)) return json.loads(out) def get_package_details(self, package): package['source'] = self.CLI return package def main(): # start work module = AnsibleModule( argument_spec=dict( clients=dict(type='list', elements='path', default=['pip']), ), supports_check_mode=True) packages = {} results = {'packages': {}} clients = module.params['clients'] found = 0 for pip in clients: if not os.path.basename(pip).startswith('pip'): module.warn('Skipping invalid pip client: %s' % (pip)) continue try: pip_mgr = PIP(pip, module) if pip_mgr.is_available(): found += 1 packages[pip] = pip_mgr.get_packages() except Exception as e: module.warn('Failed to retrieve packages with %s: %s' % (pip, to_text(e))) continue if found == 0: module.fail_json(msg='Unable to use any of the supplied pip clients: %s' % clients) # return info results['packages'] = packages module.exit_json(**results) if __name__ == '__main__': main()