forked from saltstack/salt
/
rpm.py
770 lines (655 loc) · 26 KB
/
rpm.py
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
# -*- coding: utf-8 -*-
'''
Support for rpm
'''
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import logging
import os
import re
import datetime
from salt.utils.versions import LooseVersion
# Import Salt libs
import salt.utils.decorators.path
import salt.utils.itertools
import salt.utils.path
import salt.utils.pkg.rpm
import salt.utils.versions
# pylint: disable=import-error,redefined-builtin
from salt.ext.six.moves import zip
from salt.ext import six
try:
import rpm
HAS_RPM = True
except ImportError:
HAS_RPM = False
try:
import rpmUtils.miscutils
HAS_RPMUTILS = True
except ImportError:
HAS_RPMUTILS = False
# pylint: enable=import-error,redefined-builtin
from salt.exceptions import CommandExecutionError, SaltInvocationError
log = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = 'lowpkg'
def __virtual__():
'''
Confine this module to rpm based systems
'''
if not salt.utils.path.which('rpm'):
return (False, 'The rpm execution module failed to load: rpm binary is not in the path.')
try:
os_grain = __grains__['os'].lower()
os_family = __grains__['os_family'].lower()
except Exception:
return (False, 'The rpm execution module failed to load: failed to detect os or os_family grains.')
enabled = ('amazon', 'xcp', 'xenserver', 'VirtuozzoLinux')
if os_family in ['redhat', 'suse'] or os_grain in enabled:
return __virtualname__
return (False, 'The rpm execution module failed to load: only available on redhat/suse type systems '
'or amazon, xcp or xenserver.')
def bin_pkg_info(path, saltenv='base'):
'''
.. versionadded:: 2015.8.0
Parses RPM metadata and returns a dictionary of information about the
package (name, version, etc.).
path
Path to the file. Can either be an absolute path to a file on the
minion, or a salt fileserver URL (e.g. ``salt://path/to/file.rpm``).
If a salt fileserver URL is passed, the file will be cached to the
minion so that it can be examined.
saltenv : base
Salt fileserver envrionment from which to retrieve the package. Ignored
if ``path`` is a local file path on the minion.
CLI Example:
.. code-block:: bash
salt '*' lowpkg.bin_pkg_info /root/salt-2015.5.1-2.el7.noarch.rpm
salt '*' lowpkg.bin_pkg_info salt://salt-2015.5.1-2.el7.noarch.rpm
'''
# If the path is a valid protocol, pull it down using cp.cache_file
if __salt__['config.valid_fileproto'](path):
newpath = __salt__['cp.cache_file'](path, saltenv)
if not newpath:
raise CommandExecutionError(
'Unable to retrieve {0} from saltenv \'{1}\''
.format(path, saltenv)
)
path = newpath
else:
if not os.path.exists(path):
raise CommandExecutionError(
'{0} does not exist on minion'.format(path)
)
elif not os.path.isabs(path):
raise SaltInvocationError(
'{0} does not exist on minion'.format(path)
)
# REPOID is not a valid tag for the rpm command. Remove it and replace it
# with 'none'
queryformat = salt.utils.pkg.rpm.QUERYFORMAT.replace('%{REPOID}', 'none')
output = __salt__['cmd.run_stdout'](
['rpm', '-qp', '--queryformat', queryformat, path],
output_loglevel='trace',
ignore_retcode=True,
python_shell=False
)
ret = {}
pkginfo = salt.utils.pkg.rpm.parse_pkginfo(
output,
osarch=__grains__['osarch']
)
try:
for field in pkginfo._fields:
ret[field] = getattr(pkginfo, field)
except AttributeError:
# pkginfo is None
return None
return ret
def list_pkgs(*packages):
'''
List the packages currently installed in a dict::
{'<package_name>': '<version>'}
CLI Example:
.. code-block:: bash
salt '*' lowpkg.list_pkgs
'''
pkgs = {}
cmd = ['rpm', '-q' if packages else '-qa',
'--queryformat', r'%{NAME} %{VERSION}\n']
if packages:
cmd.extend(packages)
out = __salt__['cmd.run'](cmd, output_loglevel='trace', python_shell=False)
for line in salt.utils.itertools.split(out, '\n'):
if 'is not installed' in line:
continue
comps = line.split()
pkgs[comps[0]] = comps[1]
return pkgs
def verify(*packages, **kwargs):
'''
Runs an rpm -Va on a system, and returns the results in a dict
Files with an attribute of config, doc, ghost, license or readme in the
package header can be ignored using the ``ignore_types`` keyword argument
CLI Example:
.. code-block:: bash
salt '*' lowpkg.verify
salt '*' lowpkg.verify httpd
salt '*' lowpkg.verify httpd postfix
salt '*' lowpkg.verify httpd postfix ignore_types=['config','doc']
'''
ftypes = {'c': 'config',
'd': 'doc',
'g': 'ghost',
'l': 'license',
'r': 'readme'}
ret = {}
ignore_types = kwargs.get('ignore_types', [])
if not isinstance(ignore_types, (list, six.string_types)):
raise SaltInvocationError(
'ignore_types must be a list or a comma-separated string'
)
if isinstance(ignore_types, six.string_types):
try:
ignore_types = [x.strip() for x in ignore_types.split(',')]
except AttributeError:
ignore_types = [x.strip() for x in six.text_type(ignore_types).split(',')]
verify_options = kwargs.get('verify_options', [])
if not isinstance(verify_options, (list, six.string_types)):
raise SaltInvocationError(
'verify_options must be a list or a comma-separated string'
)
if isinstance(verify_options, six.string_types):
try:
verify_options = [x.strip() for x in verify_options.split(',')]
except AttributeError:
verify_options = [x.strip() for x in six.text_type(verify_options).split(',')]
cmd = ['rpm']
cmd.extend(['--' + x for x in verify_options])
if packages:
cmd.append('-V')
# Can't concatenate a tuple, must do a list.extend()
cmd.extend(packages)
else:
cmd.append('-Va')
out = __salt__['cmd.run_all'](cmd,
output_loglevel='trace',
ignore_retcode=True,
python_shell=False)
if not out['stdout'].strip() and out['retcode'] != 0:
# If there is no stdout and the retcode is 0, then verification
# succeeded, but if the retcode is nonzero, then the command failed.
msg = 'Failed to verify package(s)'
if out['stderr']:
msg += ': {0}'.format(out['stderr'])
raise CommandExecutionError(msg)
for line in salt.utils.itertools.split(out['stdout'], '\n'):
fdict = {'mismatch': []}
if 'missing' in line:
line = ' ' + line
fdict['missing'] = True
del fdict['mismatch']
fname = line[13:]
if line[11:12] in ftypes:
fdict['type'] = ftypes[line[11:12]]
if 'type' not in fdict or fdict['type'] not in ignore_types:
if line[0:1] == 'S':
fdict['mismatch'].append('size')
if line[1:2] == 'M':
fdict['mismatch'].append('mode')
if line[2:3] == '5':
fdict['mismatch'].append('md5sum')
if line[3:4] == 'D':
fdict['mismatch'].append('device major/minor number')
if line[4:5] == 'L':
fdict['mismatch'].append('readlink path')
if line[5:6] == 'U':
fdict['mismatch'].append('user')
if line[6:7] == 'G':
fdict['mismatch'].append('group')
if line[7:8] == 'T':
fdict['mismatch'].append('mtime')
if line[8:9] == 'P':
fdict['mismatch'].append('capabilities')
ret[fname] = fdict
return ret
def modified(*packages, **flags):
'''
List the modified files that belong to a package. Not specifying any packages
will return a list of _all_ modified files on the system's RPM database.
.. versionadded:: 2015.5.0
CLI examples:
.. code-block:: bash
salt '*' lowpkg.modified httpd
salt '*' lowpkg.modified httpd postfix
salt '*' lowpkg.modified
'''
ret = __salt__['cmd.run_all'](
['rpm', '-Va'] + list(packages),
output_loglevel='trace',
python_shell=False)
data = {}
# If verification has an output, then it means it failed
# and the return code will be 1. We are interested in any bigger
# than 1 code.
if ret['retcode'] > 1:
del ret['stdout']
return ret
elif not ret['retcode']:
return data
ptrn = re.compile(r"\s+")
changes = cfg = f_name = None
for f_info in salt.utils.itertools.split(ret['stdout'], '\n'):
f_info = ptrn.split(f_info)
if len(f_info) == 3: # Config file
changes, cfg, f_name = f_info
else:
changes, f_name = f_info
cfg = None
keys = ['size', 'mode', 'checksum', 'device', 'symlink',
'owner', 'group', 'time', 'capabilities']
changes = list(changes)
if len(changes) == 8: # Older RPMs do not support capabilities
changes.append('.')
stats = []
for k, v in zip(keys, changes):
if v != '.':
stats.append(k)
if cfg is not None:
stats.append('config')
data[f_name] = stats
if not flags:
return data
# Filtering
filtered_data = {}
for f_name, stats in data.items():
include = True
for param, pval in flags.items():
if param.startswith("_"):
continue
if (not pval and param in stats) or \
(pval and param not in stats):
include = False
break
if include:
filtered_data[f_name] = stats
return filtered_data
def file_list(*packages):
'''
List the files that belong to a package. Not specifying any packages will
return a list of _every_ file on the system's rpm database (not generally
recommended).
CLI Examples:
.. code-block:: bash
salt '*' lowpkg.file_list httpd
salt '*' lowpkg.file_list httpd postfix
salt '*' lowpkg.file_list
'''
if not packages:
cmd = ['rpm', '-qla']
else:
cmd = ['rpm', '-ql']
# Can't concatenate a tuple, must do a list.extend()
cmd.extend(packages)
ret = __salt__['cmd.run'](
cmd,
output_loglevel='trace',
python_shell=False).splitlines()
return {'errors': [], 'files': ret}
def file_dict(*packages):
'''
List the files that belong to a package, sorted by group. Not specifying
any packages will return a list of _every_ file on the system's rpm
database (not generally recommended).
CLI Examples:
.. code-block:: bash
salt '*' lowpkg.file_dict httpd
salt '*' lowpkg.file_dict httpd postfix
salt '*' lowpkg.file_dict
'''
errors = []
ret = {}
pkgs = {}
cmd = ['rpm', '-q' if packages else '-qa',
'--queryformat', r'%{NAME} %{VERSION}\n']
if packages:
cmd.extend(packages)
out = __salt__['cmd.run'](cmd, output_loglevel='trace', python_shell=False)
for line in salt.utils.itertools.split(out, '\n'):
if 'is not installed' in line:
errors.append(line)
continue
comps = line.split()
pkgs[comps[0]] = {'version': comps[1]}
for pkg in pkgs:
files = []
cmd = ['rpm', '-ql', pkg]
out = __salt__['cmd.run'](
['rpm', '-ql', pkg],
output_loglevel='trace',
python_shell=False)
ret[pkg] = out.splitlines()
return {'errors': errors, 'packages': ret}
def owner(*paths):
'''
Return the name of the package that owns the file. Multiple file paths can
be passed. If a single path is passed, a string will be returned,
and if multiple paths are passed, a dictionary of file/package name pairs
will be returned.
If the file is not owned by a package, or is not present on the minion,
then an empty string will be returned for that path.
CLI Examples:
.. code-block:: bash
salt '*' lowpkg.owner /usr/bin/apachectl
salt '*' lowpkg.owner /usr/bin/apachectl /etc/httpd/conf/httpd.conf
'''
if not paths:
return ''
ret = {}
for path in paths:
cmd = ['rpm', '-qf', '--queryformat', '%{name}', path]
ret[path] = __salt__['cmd.run_stdout'](cmd,
output_loglevel='trace',
python_shell=False)
if 'not owned' in ret[path].lower():
ret[path] = ''
if len(ret) == 1:
return list(ret.values())[0]
return ret
@salt.utils.decorators.path.which('rpm2cpio')
@salt.utils.decorators.path.which('cpio')
@salt.utils.decorators.path.which('diff')
def diff(package_path, path):
'''
Return a formatted diff between current file and original in a package.
NOTE: this function includes all files (configuration and not), but does
not work on binary content.
:param package: Full pack of the RPM file
:param path: Full path to the installed file
:return: Difference or empty string. For binary files only a notification.
CLI example:
.. code-block:: bash
salt '*' lowpkg.diff /path/to/apache2.rpm /etc/apache2/httpd.conf
'''
cmd = "rpm2cpio {0} " \
"| cpio -i --quiet --to-stdout .{1} " \
"| diff -u --label 'A {1}' --from-file=- --label 'B {1}' {1}"
res = __salt__['cmd.shell'](cmd.format(package_path, path),
output_loglevel='trace')
if res and res.startswith('Binary file'):
return 'File \'{0}\' is binary and its content has been ' \
'modified.'.format(path)
return res
def info(*packages, **kwargs):
'''
Return a detailed package(s) summary information.
If no packages specified, all packages will be returned.
:param packages:
:param attr:
Comma-separated package attributes. If no 'attr' is specified, all available attributes returned.
Valid attributes are:
version, vendor, release, build_date, build_date_time_t, install_date, install_date_time_t,
build_host, group, source_rpm, arch, epoch, size, license, signature, packager, url, summary, description.
:param all_versions:
Return information for all installed versions of the packages
:return:
CLI example:
.. code-block:: bash
salt '*' lowpkg.info apache2 bash
salt '*' lowpkg.info apache2 bash attr=version
salt '*' lowpkg.info apache2 bash attr=version,build_date_iso,size
salt '*' lowpkg.info apache2 bash attr=version,build_date_iso,size all_versions=True
'''
all_versions = kwargs.get('all_versions', False)
# LONGSIZE is not a valid tag for all versions of rpm. If LONGSIZE isn't
# available, then we can just use SIZE for older versions. See Issue #31366.
rpm_tags = __salt__['cmd.run_stdout'](
['rpm', '--querytags'],
python_shell=False).splitlines()
if 'LONGSIZE' in rpm_tags:
size_tag = '%{LONGSIZE}'
else:
size_tag = '%{SIZE}'
cmd = packages and "rpm -q {0}".format(' '.join(packages)) or "rpm -qa"
# Construct query format
attr_map = {
"name": "name: %{NAME}\\n",
"relocations": "relocations: %|PREFIXES?{[%{PREFIXES} ]}:{(not relocatable)}|\\n",
"version": "version: %{VERSION}\\n",
"vendor": "vendor: %{VENDOR}\\n",
"release": "release: %{RELEASE}\\n",
"epoch": "%|EPOCH?{epoch: %{EPOCH}\\n}|",
"build_date_time_t": "build_date_time_t: %{BUILDTIME}\\n",
"build_date": "build_date: %{BUILDTIME}\\n",
"install_date_time_t": "install_date_time_t: %|INSTALLTIME?{%{INSTALLTIME}}:{(not installed)}|\\n",
"install_date": "install_date: %|INSTALLTIME?{%{INSTALLTIME}}:{(not installed)}|\\n",
"build_host": "build_host: %{BUILDHOST}\\n",
"group": "group: %{GROUP}\\n",
"source_rpm": "source_rpm: %{SOURCERPM}\\n",
"size": "size: " + size_tag + "\\n",
"arch": "arch: %{ARCH}\\n",
"license": "%|LICENSE?{license: %{LICENSE}\\n}|",
"signature": "signature: %|DSAHEADER?{%{DSAHEADER:pgpsig}}:{%|RSAHEADER?{%{RSAHEADER:pgpsig}}:"
"{%|SIGGPG?{%{SIGGPG:pgpsig}}:{%|SIGPGP?{%{SIGPGP:pgpsig}}:{(none)}|}|}|}|\\n",
"packager": "%|PACKAGER?{packager: %{PACKAGER}\\n}|",
"url": "%|URL?{url: %{URL}\\n}|",
"summary": "summary: %{SUMMARY}\\n",
"description": "description:\\n%{DESCRIPTION}\\n",
"edition": "edition: %|EPOCH?{%{EPOCH}:}|%{VERSION}-%{RELEASE}\\n",
}
attr = kwargs.get('attr', None) and kwargs['attr'].split(",") or None
query = list()
if attr:
for attr_k in attr:
if attr_k in attr_map and attr_k != 'description':
query.append(attr_map[attr_k])
if not query:
raise CommandExecutionError('No valid attributes found.')
if 'name' not in attr:
attr.append('name')
query.append(attr_map['name'])
if 'edition' not in attr:
attr.append('edition')
query.append(attr_map['edition'])
else:
for attr_k, attr_v in six.iteritems(attr_map):
if attr_k != 'description':
query.append(attr_v)
if attr and 'description' in attr or not attr:
query.append(attr_map['description'])
query.append("-----\\n")
call = __salt__['cmd.run_all'](cmd + (" --queryformat '{0}'".format(''.join(query))),
output_loglevel='trace', env={'TZ': 'UTC'}, clean_env=True)
if call['retcode'] != 0:
comment = ''
if 'stderr' in call:
comment += (call['stderr'] or call['stdout'])
raise CommandExecutionError(comment)
elif 'error' in call['stderr']:
raise CommandExecutionError(call['stderr'])
else:
out = call['stdout']
_ret = list()
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
if key == 'description':
descr_marker = True
continue
if key == 'name':
pkg_name = value
# Convert Unix ticks into ISO time format
if key in ['build_date', 'install_date']:
try:
pkg_data[key] = datetime.datetime.utcfromtimestamp(int(value)).isoformat() + "Z"
except ValueError:
log.warning('Could not convert "%s" into Unix time', value)
continue
# Convert Unix ticks into an Integer
if key in ['build_date_time_t', 'install_date_time_t']:
try:
pkg_data[key] = int(value)
except ValueError:
log.warning('Could not convert "%s" into Unix time', value)
continue
if key not in ['description', 'name'] and value:
pkg_data[key] = value
if attr and 'description' in attr or not attr:
pkg_data['description'] = os.linesep.join(descr)
if pkg_name:
pkg_data['name'] = pkg_name
_ret.append(pkg_data)
# Force-sort package data by version,
# pick only latest versions
# (in case multiple packages installed, e.g. kernel)
ret = dict()
for pkg_data in reversed(sorted(_ret, key=lambda x: LooseVersion(x['edition']))):
pkg_name = pkg_data.pop('name')
# Filter out GPG public keys packages
if pkg_name.startswith('gpg-pubkey'):
continue
if pkg_name not in ret:
if all_versions:
ret[pkg_name] = [pkg_data.copy()]
else:
ret[pkg_name] = pkg_data.copy()
del ret[pkg_name]['edition']
elif all_versions:
ret[pkg_name].append(pkg_data.copy())
return ret
def version_cmp(ver1, ver2, ignore_epoch=False):
'''
.. versionadded:: 2015.8.9
Do a cmp-style comparison on two packages. Return -1 if ver1 < ver2, 0 if
ver1 == ver2, and 1 if ver1 > ver2. Return None if there was a problem
making the comparison.
ignore_epoch : False
Set to ``True`` to ignore the epoch when comparing versions
.. versionadded:: 2015.8.10,2016.3.2
CLI Example:
.. code-block:: bash
salt '*' pkg.version_cmp '0.2-001' '0.2.0.1-002'
'''
normalize = lambda x: six.text_type(x).split(':', 1)[-1] \
if ignore_epoch \
else six.text_type(x)
ver1 = normalize(ver1)
ver2 = normalize(ver2)
try:
cmp_func = None
if HAS_RPM:
try:
cmp_func = rpm.labelCompare
except AttributeError:
# Catches corner case where someone has a module named "rpm" in
# their pythonpath.
log.debug(
'rpm module imported, but it does not have the '
'labelCompare function. Not using rpm.labelCompare for '
'version comparison.'
)
if cmp_func is None and HAS_RPMUTILS:
try:
cmp_func = rpmUtils.miscutils.compareEVR
except AttributeError:
log.debug('rpmUtils.miscutils.compareEVR is not available')
if cmp_func is None:
if salt.utils.path.which('rpmdev-vercmp'):
# rpmdev-vercmp always uses epochs, even when zero
def _ensure_epoch(ver):
def _prepend(ver):
return '0:{0}'.format(ver)
try:
if ':' not in ver:
return _prepend(ver)
except TypeError:
return _prepend(ver)
return ver
ver1 = _ensure_epoch(ver1)
ver2 = _ensure_epoch(ver2)
result = __salt__['cmd.run_all'](
['rpmdev-vercmp', ver1, ver2],
python_shell=False,
redirect_stderr=True,
ignore_retcode=True)
# rpmdev-vercmp returns 0 on equal, 11 on greater-than, and
# 12 on less-than.
if result['retcode'] == 0:
return 0
elif result['retcode'] == 11:
return 1
elif result['retcode'] == 12:
return -1
else:
# We'll need to fall back to salt.utils.versions.version_cmp()
log.warning(
'Failed to interpret results of rpmdev-vercmp output. '
'This is probably a bug, and should be reported. '
'Return code was %s. Output: %s',
result['retcode'], result['stdout']
)
else:
# We'll need to fall back to salt.utils.versions.version_cmp()
log.warning(
'rpmdevtools is not installed, please install it for '
'more accurate version comparisons'
)
else:
# If one EVR is missing a release but not the other and they
# otherwise would be equal, ignore the release. This can happen if
# e.g. you are checking if a package version 3.2 is satisfied by
# 3.2-1.
(ver1_e, ver1_v, ver1_r) = salt.utils.pkg.rpm.version_to_evr(ver1)
(ver2_e, ver2_v, ver2_r) = salt.utils.pkg.rpm.version_to_evr(ver2)
if not ver1_r or not ver2_r:
ver1_r = ver2_r = ''
cmp_result = cmp_func((ver1_e, ver1_v, ver1_r),
(ver2_e, ver2_v, ver2_r))
if cmp_result not in (-1, 0, 1):
raise CommandExecutionError(
'Comparison result \'{0}\' is invalid'.format(cmp_result)
)
return cmp_result
except Exception as exc:
log.warning(
'Failed to compare version \'%s\' to \'%s\' using RPM: %s',
ver1, ver2, exc
)
# We would already have normalized the versions at the beginning of this
# function if ignore_epoch=True, so avoid unnecessary work and just pass
# False for this value.
return salt.utils.versions.version_cmp(ver1, ver2, ignore_epoch=False)
def checksum(*paths):
'''
Return if the signature of a RPM file is valid.
CLI Example:
.. code-block:: bash
salt '*' lowpkg.checksum /path/to/package1.rpm
salt '*' lowpkg.checksum /path/to/package1.rpm /path/to/package2.rpm
'''
ret = dict()
if not paths:
raise CommandExecutionError("No package files has been specified.")
for package_file in paths:
ret[package_file] = (bool(__salt__['file.file_exists'](package_file)) and
not __salt__['cmd.retcode'](["rpm", "-K", "--quiet", package_file],
ignore_retcode=True,
output_loglevel='trace',
python_shell=False))
return ret