1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
#!/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:
- 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()
|