diff --git a/salt/modules/rpm.py b/salt/modules/rpm.py index 2408c1422b4f..25d748473034 100644 --- a/salt/modules/rpm.py +++ b/salt/modules/rpm.py @@ -9,6 +9,7 @@ # Import Salt libs import salt.utils +import salt.utils.decorators as decorators log = logging.getLogger(__name__) @@ -222,3 +223,33 @@ def owner(*paths): if len(ret) == 1: return list(ret.values())[0] return ret + + +@decorators.which('rpm2cpio') +@decorators.which('cpio') +@decorators.which('diff') +def diff(package, 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: The name of the package + :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 apache2 /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), 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 diff --git a/salt/modules/zypper.py b/salt/modules/zypper.py index d493ff00b121..a47232087d7d 100644 --- a/salt/modules/zypper.py +++ b/salt/modules/zypper.py @@ -315,7 +315,7 @@ def del_repo(repo): return { repo: True, 'message': msg[0].childNodes[0].nodeValue, - } + } raise CommandExecutionError('Repository \'{0}\' not found.'.format(repo)) @@ -662,7 +662,7 @@ def upgrade(refresh=True): ret = {'changes': {}, 'result': True, 'comment': '', - } + } if salt.utils.is_true(refresh): refresh_db() @@ -1092,3 +1092,71 @@ def list_products(): ret[product.pop('name')] = product return ret + + +def download(*packages): + """ + Download packages to the local disk. + + CLI example: + + .. code-block:: bash + + salt '*' pkg.download httpd + salt '*' pkg.download httpd postfix + """ + if not packages: + raise CommandExecutionError("No packages has been specified.") + + doc = dom.parseString(__salt__['cmd.run']( + ('zypper -x --non-interactive download {0}'.format(' '.join(packages))), + output_loglevel='trace')) + pkg_ret = {} + for dld_result in doc.getElementsByTagName("download-result"): + repo = dld_result.getElementsByTagName("repository")[0] + pkg_info = { + 'repository-name': repo.getAttribute("name"), + 'repository-alias': repo.getAttribute("alias"), + 'path': dld_result.getElementsByTagName("localfile")[0].getAttribute("path"), + } + pkg_ret[_get_first_aggregate_text(dld_result.getElementsByTagName("name"))] = pkg_info + + if pkg_ret: + return pkg_ret + + raise CommandExecutionError("Unable to download packages: {0}.".format(', '.join(packages))) + + +def diff(*paths): + ''' + Return a formatted diff between current files and original in a package. + NOTE: this function includes all files (configuration and not), but does + not work on binary content. + + :param path: Full path to the installed file + :return: Difference string or raises and exception if examined file is binary. + + CLI example: + + .. code-block:: bash + + salt '*' pkg.diff /etc/apache2/httpd.conf /etc/sudoers + ''' + ret = {} + + pkg_to_paths = {} + for pth in paths: + pth_pkg = __salt__['lowpkg.owner'](pth) + if not pth_pkg: + ret[pth] = os.path.exists(pth) and 'Not managed' or 'N/A' + else: + if pkg_to_paths.get(pth_pkg) is None: + pkg_to_paths[pth_pkg] = [] + pkg_to_paths[pth_pkg].append(pth) + + local_pkgs = __salt__['pkg.download'](*pkg_to_paths.keys()) + for pkg, files in pkg_to_paths.items(): + for path in files: + ret[path] = __salt__['lowpkg.diff'](local_pkgs[pkg]['path'], path) or 'Unchanged' + + return ret