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 194f5a74f45c..0b0d8d6b95ae 100644 --- a/salt/modules/zypper.py +++ b/salt/modules/zypper.py @@ -332,7 +332,7 @@ def del_repo(repo): return { repo: True, 'message': msg[0].childNodes[0].nodeValue, - } + } raise CommandExecutionError('Repository \'{0}\' not found.'.format(repo)) @@ -398,11 +398,11 @@ def mod_repo(repo, **kwargs): new_url = _urlparse(url) if not new_url.path: new_url = _urlparse.ParseResult(scheme=new_url.scheme, # pylint: disable=E1123 - netloc=new_url.netloc, - path='/', - params=new_url.params, - query=new_url.query, - fragment=new_url.fragment) + netloc=new_url.netloc, + path='/', + params=new_url.params, + query=new_url.query, + fragment=new_url.fragment) base_url = _urlparse(repo_meta['baseurl']) if new_url == base_url: @@ -460,7 +460,7 @@ def mod_repo(repo, **kwargs): # If repo nor added neither modified, error should be thrown if not added and not cmd_opt: raise CommandExecutionError( - 'Modification of the repository \'{0}\' was not specified.'.format(repo)) + 'Modification of the repository \'{0}\' was not specified.'.format(repo)) return get_repo(repo) @@ -686,7 +686,7 @@ def upgrade(refresh=True): ret = {'changes': {}, 'result': True, 'comment': '', - } + } if salt.utils.is_true(refresh): refresh_db() @@ -1045,7 +1045,7 @@ def search(criteria): out = {} for solvable in [s for s in solvables if s.getAttribute('status') == 'not-installed' and - s.getAttribute('kind') == 'package']: + s.getAttribute('kind') == 'package']: out[solvable.getAttribute('name')] = { 'summary': solvable.getAttribute('summary') } @@ -1114,3 +1114,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