Merge pull request #27134 from isbm/isbm-pkg.info-backport-2015.8

Backport to 2015.8: "pkg.info"
This commit is contained in:
Mike Place 2015-09-15 09:57:46 -06:00
commit 0d8248930e
5 changed files with 339 additions and 5 deletions

View file

@ -2217,3 +2217,37 @@ def owner(*paths):
if len(ret) == 1:
return next(six.itervalues(ret))
return ret
def info_installed(*names):
'''
Return the information of the named package(s), installed on the system.
CLI example:
.. code-block:: bash
salt '*' pkg.info_installed <package1>
salt '*' pkg.info_installed <package1> <package2> <package3> ...
'''
ret = dict()
for pkg_name, pkg_nfo in __salt__['lowpkg.info'](*names).items():
t_nfo = dict()
# Translate dpkg-specific keys to a common structure
for key, value in pkg_nfo.items():
if key == 'package':
t_nfo['name'] = value
elif key == 'origin':
t_nfo['vendor'] = value
elif key == 'section':
t_nfo['group'] = value
elif key == 'maintainer':
t_nfo['packager'] = value
elif key == 'homepage':
t_nfo['url'] = value
else:
t_nfo[key] = value
ret[pkg_name] = t_nfo
return ret

View file

@ -7,6 +7,8 @@ from __future__ import absolute_import
# Import python libs
import logging
import os
import re
import datetime
# Import salt libs
import salt.utils
@ -241,3 +243,154 @@ def file_dict(*packages):
files.append(line)
ret[pkg] = files
return {'errors': errors, 'packages': ret}
def _get_pkg_info(*packages):
'''
Return list of package informations. If 'packages' parameter is empty,
then data about all installed packages will be returned.
:param packages: Specified packages.
:return:
'''
ret = list()
cmd = "dpkg-query -W -f='package:${binary:Package}\\n" \
"revision:${binary:Revision}\\n" \
"architecture:${Architecture}\\n" \
"maintainer:${Maintainer}\\n" \
"summary:${Summary}\\n" \
"source:${source:Package}\\n" \
"version:${Version}\\n" \
"section:${Section}\\n" \
"installed_size:${Installed-size}\\n" \
"size:${Size}\\n" \
"MD5:${MD5sum}\\n" \
"SHA1:${SHA1}\\n" \
"SHA256:${SHA256}\\n" \
"origin:${Origin}\\n" \
"homepage:${Homepage}\\n" \
"======\\n" \
"description:${Description}\\n" \
"------\\n'"
cmd += ' {0}'.format(' '.join(packages))
cmd = cmd.strip()
call = __salt__['cmd.run_all'](cmd, python_chell=False)
if call['retcode']:
raise CommandExecutionError("Error getting packages information: {0}".format(call['stderr']))
for pkg_info in [elm for elm in re.split(r"----*", call['stdout']) if elm.strip()]:
pkg_data = dict()
pkg_info, pkg_descr = re.split(r"====*", pkg_info)
for pkg_info_line in [el.strip() for el in pkg_info.split(os.linesep) if el.strip()]:
key, value = pkg_info_line.split(":", 1)
if value:
pkg_data[key] = value
pkg_data['install_date'] = _get_pkg_install_time(pkg_data.get('package'))
pkg_data['description'] = pkg_descr.split(":", 1)[-1]
ret.append(pkg_data)
return ret
def _get_pkg_license(pkg):
'''
Try to get a license from the package.
Based on https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
:param pkg:
:return:
'''
licenses = set()
cpr = "/usr/share/doc/{0}/copyright".format(pkg)
if os.path.exists(cpr):
for line in open(cpr).read().split(os.linesep):
if line.startswith("License:"):
licenses.add(line.split(":", 1)[1].strip())
return ", ".join(sorted(licenses))
def _get_pkg_install_time(pkg):
'''
Return package install time, based on the /var/lib/dpkg/info/<package>.list
:return:
'''
iso_time = "N/A"
if pkg is not None:
location = "/var/lib/dpkg/info/{0}.list".format(pkg)
if os.path.exists(location):
iso_time = datetime.datetime.fromtimestamp(os.path.getmtime(location)).isoformat()
return iso_time
def _get_pkg_ds_avail():
'''
Get the package information of the available packages, maintained by dselect.
Note, this will be not very useful, if dselect isn't installed.
:return:
'''
avail = "/var/lib/dpkg/available"
if not salt.utils.which('dselect') or not os.path.exists(avail):
return dict()
# Do not update with dselect, just read what is.
ret = dict()
pkg_mrk = "Package:"
pkg_name = "package"
for pkg_info in open(avail).read().split(pkg_mrk):
nfo = dict()
for line in (pkg_mrk + pkg_info).split(os.linesep):
line = line.split(": ", 1)
if len(line) != 2:
continue
key, value = line
if value.strip():
nfo[key.lower()] = value
if nfo.get(pkg_name):
ret[nfo[pkg_name]] = nfo
return ret
def info(*packages):
'''
Return a detailed package(s) summary information.
If no packages specified, all packages will be returned.
:param packages:
:return:
CLI example:
.. code-block:: bash
salt '*' lowpkg.info apache2 bash
'''
# Get the missing information from the /var/lib/dpkg/available, if it is there.
# However, this file is operated by dselect which has to be installed.
dselect_pkg_avail = _get_pkg_ds_avail()
ret = dict()
for pkg in _get_pkg_info(*packages):
# Merge extra information from the dselect, if available
for pkg_ext_k, pkg_ext_v in dselect_pkg_avail.get(pkg['package'], {}).items():
if pkg_ext_k not in pkg:
pkg[pkg_ext_k] = pkg_ext_v
# Remove "technical" keys
for t_key in ['installed_size', 'depends', 'recommends',
'provides', 'replaces', 'conflicts', 'bugs',
'description-md5', 'task']:
if t_key in pkg:
del pkg[t_key]
lic = _get_pkg_license(pkg['package'])
if lic:
pkg['license'] = lic
ret[pkg['package']] = pkg
return ret

View file

@ -8,6 +8,8 @@ from __future__ import absolute_import
import logging
import os
import re
import time
import datetime
# Import Salt libs
import salt.utils
@ -395,3 +397,78 @@ def diff(package, path):
return 'File "{0}" is binary and its content has been modified.'.format(path)
return res
def _pkg_time_to_iso(pkg_time):
'''
Convert package time to ISO 8601.
:param pkg_time:
:return:
'''
ptime = time.strptime(pkg_time)
return datetime.datetime(ptime.tm_year, ptime.tm_mon, ptime.tm_mday,
ptime.tm_hour, ptime.tm_min, ptime.tm_sec).isoformat()
def info(*packages):
'''
Return a detailed package(s) summary information.
If no packages specified, all packages will be returned.
:param packages:
:return:
CLI example:
.. code-block:: bash
salt '*' lowpkg.info apache2 bash
'''
cmd = packages and "rpm -qi {0}".format(' '.join(packages)) or "rpm -qai"
call = __salt__['cmd.run_all'](cmd + " --queryformat '-----\n'", output_loglevel='trace')
if call['retcode'] != 0:
comment = ''
if 'stderr' in call:
comment += call['stderr']
raise CommandExecutionError('{0}'.format(comment))
else:
out = call['stdout']
ret = dict()
for pkg_info in re.split(r"----*", out):
pkg_info = pkg_info.strip()
if not pkg_info:
continue
pkg_info = pkg_info.split(os.linesep)
if pkg_info[-1].lower().startswith('distribution'):
pkg_info = pkg_info[:-1]
pkg_data = dict()
pkg_name = None
descr_marker = False
descr = list()
for line in pkg_info:
if descr_marker:
descr.append(line)
continue
line = [item.strip() for item in line.split(':', 1)]
if len(line) != 2:
continue
key, value = line
key = key.replace(' ', '_').lower()
if key == 'description':
descr_marker = True
continue
if key == 'name':
pkg_name = value
if key in ['build_date', 'install_date']:
value = _pkg_time_to_iso(value)
if key != 'description' and value:
pkg_data[key] = value
pkg_data['decription'] = os.linesep.join(descr)
if pkg_name:
ret[pkg_name] = pkg_data
return ret

View file

@ -684,6 +684,32 @@ def list_upgrades(refresh=True, **kwargs):
return dict([(x.name, x.version) for x in updates])
def info_installed(*names):
'''
Return the information of the named package(s), installed on the system.
CLI example:
.. code-block:: bash
salt '*' pkg.info_installed <package1>
salt '*' pkg.info_installed <package1> <package2> <package3> ...
'''
ret = dict()
for pkg_name, pkg_nfo in __salt__['lowpkg.info'](*names).items():
t_nfo = dict()
# Translate dpkg-specific keys to a common structure
for key, value in pkg_nfo.items():
if key == 'source_rpm':
t_nfo['source'] = value
else:
t_nfo[key] = value
ret[pkg_name] = t_nfo
return ret
def check_db(*names, **kwargs):
'''
.. versionadded:: 0.17.0

View file

@ -95,7 +95,33 @@ def list_upgrades(refresh=True):
list_updates = list_upgrades
def info(*names, **kwargs):
def info_installed(*names):
'''
Return the information of the named package(s), installed on the system.
CLI example:
.. code-block:: bash
salt '*' pkg.info_installed <package1>
salt '*' pkg.info_installed <package1> <package2> <package3> ...
'''
ret = dict()
for pkg_name, pkg_nfo in __salt__['lowpkg.info'](*names).items():
t_nfo = dict()
# Translate dpkg-specific keys to a common structure
for key, value in pkg_nfo.items():
if key == 'source_rpm':
t_nfo['source'] = value
else:
t_nfo[key] = value
ret[pkg_name] = t_nfo
return ret
def info_available(*names, **kwargs):
'''
Return the information of the named package available for the system.
@ -103,8 +129,8 @@ def info(*names, **kwargs):
.. code-block:: bash
salt '*' pkg.info <package name>
salt '*' pkg.info <package1> <package2> <package3> ...
salt '*' pkg.info_available <package1>
salt '*' pkg.info_available <package1> <package2> <package3> ...
'''
ret = {}
@ -124,7 +150,7 @@ def info(*names, **kwargs):
# Run in batches
while batch:
cmd = 'zypper info -t package {0}'.format(' '.join(batch[:batch_size]))
pkg_info.extend(re.split("----*", __salt__['cmd.run_stdout'](cmd, output_loglevel='trace')))
pkg_info.extend(re.split(r"----*", __salt__['cmd.run_stdout'](cmd, output_loglevel='trace')))
batch = batch[batch_size:]
for pkg_data in pkg_info:
@ -140,6 +166,24 @@ def info(*names, **kwargs):
return ret
def info(*names, **kwargs):
'''
.. deprecated:: Nitrogen
Use :py:func:`~salt.modules.pkg.info_available` instead.
Return the information of the named package available for the system.
CLI example:
.. code-block:: bash
salt '*' pkg.info <package1>
salt '*' pkg.info <package1> <package2> <package3> ...
'''
salt.utils.warn_until('Nitrogen', "Please use 'pkg.info_available' instead")
return info_available(*names)
def latest_version(*names, **kwargs):
'''
Return the latest version of the named package available for upgrade or
@ -162,7 +206,7 @@ def latest_version(*names, **kwargs):
return ret
names = sorted(list(set(names)))
package_info = info(*names)
package_info = info_available(*names)
for name in names:
pkg_info = package_info.get(name)
if pkg_info is not None and pkg_info.get('status', '').lower() in ['not installed', 'out-of-date']: