-
Notifications
You must be signed in to change notification settings - Fork 23.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
scan_packages: made adding package managers easier #49079
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
7e52660
made adding package managers easier
bcoca a09313a
moar pkg mgrs and moar info
bcoca 1e07299
updated clgo
bcoca 2157886
Updates from feedback
webknjaz 4dfffe8
incorporated more feedback and added docstrings
bcoca cffc21a
moar from feedback
bcoca 7e069b7
more targetted errors/warnings
bcoca e83060f
added strategy, reordered to conserve priority
bcoca 4e1279d
rpm > apt
bcoca e963aca
move break to top
bcoca d274dab
fix trate
bcoca ad55a37
piping it
bcoca 3654b73
lines and meta
bcoca 09dd148
refactored common functions
bcoca a59c717
missing license
bcoca d2b4494
avoid facts
bcoca 18d7943
update clog
bcoca 6c49a6e
addressed feedback
bcoca fe71be7
fix clog
bcoca 8115116
cleanup
bcoca ebffad6
upd
bcoca 9620f67
removed pip as that was removed
bcoca 7dbd4dc
renamed cpan
bcoca c71876a
added a single line since 2 lines are needed to be
bcoca eae72f7
fix internal ref
bcoca 35b29ce
not intended in this round
bcoca 9b75a46
updated as per fb
bcoca File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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): | ||
bcoca marked this conversation as resolved.
Show resolved
Hide resolved
|
||
found = False | ||
try: | ||
self._lib = __import__(self.LIB) | ||
bcoca marked this conversation as resolved.
Show resolved
Hide resolved
|
||
found = True | ||
bcoca marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) | ||
bcoca marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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' | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
''' | ||||||
|
||||||
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.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i don't see why we need to do extra work here