Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: diff support for package managed files #21769

Merged
merged 11 commits into from
Mar 18, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions salt/modules/rpm.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

# Import Salt libs
import salt.utils
import salt.utils.decorators as decorators

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -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
86 changes: 77 additions & 9 deletions salt/modules/zypper.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -686,7 +686,7 @@ def upgrade(refresh=True):
ret = {'changes': {},
'result': True,
'comment': '',
}
}

if salt.utils.is_true(refresh):
refresh_db()
Expand Down Expand Up @@ -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')
}
Expand Down Expand Up @@ -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