forked from ansible/ansible
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
scan_packages: made adding package managers easier (ansible#49079)
* made adding package managers easier added portage support * moar pkg mgrs and moar info - added 'pkg' pkg manager (freebsd) - added pip - more apt info * updated clgo * Updates from feedback Co-Authored-By: bcoca <[email protected]> * incorporated more feedback and added docstrings * moar from feedback - made manager list dynamic and names based on class - better not found msg - made abstract metaclass again - test is now init exception - module to global - better dedupe comments * more targetted errors/warnings * added strategy, reordered to conserve priority * rpm > apt * move break to top * fix trate * piping it * lines and meta * refactored common functions - moved pip into it's own module - cleaned up base clases - ensure 'lower' match in package_facts * missing license * avoid facts * update clog * addressed feedback * fix clog * cleanup * upd * removed pip as that was removed * renamed cpan * added a single line since 2 lines are needed to be readabnle instead of just 1 line, it is a huge problem otherwise * fix internal ref * not intended in this round * updated as per fb
- Loading branch information
Showing
5 changed files
with
402 additions
and
84 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
minor_changes: | ||
- package_facts, now supports multiple package managers per system. | ||
New systems supported include Gentoo's portage with portage-utils installed, as well as FreeBSD's pkg |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# (c) 2018, Ansible Project | ||
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) | ||
|
||
from __future__ import absolute_import, division, print_function | ||
__metaclass__ = type | ||
|
||
from abc import ABCMeta, abstractmethod | ||
|
||
from ansible.module_utils.six import with_metaclass | ||
from ansible.module_utils.basic import get_all_subclasses | ||
from ansible.module_utils.common.process import get_bin_path | ||
|
||
|
||
def get_all_pkg_managers(): | ||
|
||
return dict([(obj.__name__.lower(), obj) for obj in get_all_subclasses(PkgMgr) if obj not in (CLIMgr, LibMgr)]) | ||
|
||
|
||
class PkgMgr(with_metaclass(ABCMeta, object)): | ||
|
||
@abstractmethod | ||
def is_available(self): | ||
# This method is supposed to return True/False if the package manager is currently installed/usable | ||
# It can also 'prep' the required systems in the process of detecting availability | ||
pass | ||
|
||
@abstractmethod | ||
def list_installed(self): | ||
# This method should return a list of installed packages, each list item will be passed to get_package_details | ||
pass | ||
|
||
@abstractmethod | ||
def get_package_details(self, package): | ||
# This takes a 'package' item and returns a dictionary with the package information, name and version are minimal requirements | ||
pass | ||
|
||
def get_packages(self): | ||
# Take all of the above and return a dictionary of lists of dictionaries (package = list of installed versions) | ||
|
||
installed_packages = {} | ||
for package in self.list_installed(): | ||
package_details = self.get_package_details(package) | ||
if 'source' not in package_details: | ||
package_details['source'] = self.__class__.__name__.lower() | ||
name = package_details['name'] | ||
if name not in installed_packages: | ||
installed_packages[name] = [package_details] | ||
else: | ||
installed_packages[name].append(package_details) | ||
return installed_packages | ||
|
||
|
||
class LibMgr(PkgMgr): | ||
|
||
LIB = None | ||
|
||
def __init__(self): | ||
|
||
self._lib = None | ||
super(LibMgr, self).__init__() | ||
|
||
def is_available(self): | ||
found = False | ||
try: | ||
self._lib = __import__(self.LIB) | ||
found = True | ||
except ImportError: | ||
pass | ||
return found | ||
|
||
|
||
class CLIMgr(PkgMgr): | ||
|
||
CLI = None | ||
|
||
def __init__(self): | ||
|
||
self._cli = None | ||
super(CLIMgr, self).__init__() | ||
|
||
def is_available(self): | ||
self._cli = get_bin_path(self.CLI, False) | ||
return bool(self._cli) |
152 changes: 152 additions & 0 deletions
152
lib/ansible/modules/packaging/language/pip_package_info.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
#!/usr/bin/python | ||
# (c) 2018, Ansible Project | ||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
|
||
# started out with 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: pip_package_info | ||
short_description: pip package information | ||
description: | ||
- Return information about installed pip packages | ||
version_added: "2.8" | ||
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, i.e `pip3.7`. | ||
default: ['pip'] | ||
required: False | ||
type: list | ||
requirements: | ||
- 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 | ||
pip_package_info: | ||
- name: get the facts for default pip, pip2 and pip3.6 | ||
pip_package_info: | ||
clients: ['pip', 'pip2', 'pip3.6'] | ||
- name: get from specific paths (virtualenvs?) | ||
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._text 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): | ||
|
||
self.CLI = pip | ||
|
||
def list_installed(self): | ||
global module | ||
rc, out, err = 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 | ||
global module | ||
module = AnsibleModule(argument_spec=dict(clients={'type': 'list', '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) | ||
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() |
Oops, something went wrong.