Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-12-SP4:GA
salt.3892
0031-Port-rsync-state-from-2016.3.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File 0031-Port-rsync-state-from-2016.3.patch of Package salt.3892
From c7a06f6029ea1c8c7e61916d5bd09992d25702e3 Mon Sep 17 00:00:00 2001 From: Bo Maryniuk <bo@suse.de> Date: Fri, 25 Nov 2016 15:34:33 +0100 Subject: [PATCH 31/38] Port rsync state from 2016.3 * Port rsync state from 2016.3 --- salt/modules/rsync.py | 115 +++++++++++++++++++++----------------- salt/states/rsync.py | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 214 insertions(+), 50 deletions(-) create mode 100644 salt/states/rsync.py diff --git a/salt/modules/rsync.py b/salt/modules/rsync.py index 36763e2..35e518f 100644 --- a/salt/modules/rsync.py +++ b/salt/modules/rsync.py @@ -10,16 +10,17 @@ Options passed into opts will overwrite options passed into pillar. from __future__ import absolute_import # Import python libs +import errno import logging -import os # Import salt libs -from salt.exceptions import CommandExecutionError +import salt.utils +from salt.exceptions import CommandExecutionError, SaltInvocationError log = logging.getLogger(__name__) -def _check(delete, force, update, passwordfile, exclude, excludefrom): +def _check(delete, force, update, passwordfile, exclude, excludefrom, dryrun): ''' Generate rsync options ''' @@ -32,15 +33,16 @@ def _check(delete, force, update, passwordfile, exclude, excludefrom): if update: options.append('--update') if passwordfile: - options.append('--password-file={0}'.format(passwordfile)) + options.extend(['--password-file', passwordfile]) if excludefrom: - options.append('--exclude-from={0}'.format(excludefrom)) + options.extend(['--exclude-from', excludefrom]) if exclude: - exclude = None + exclude = False if exclude: - options.append('--exclude={0}'.format(exclude)) - - return ' '.join(options) + options.extend(['--exclude', exclude]) + if dryrun: + options.append('--dry-run') + return options def rsync(src, @@ -51,8 +53,12 @@ def rsync(src, passwordfile=None, exclude=None, excludefrom=None, - ): + dryrun=False): ''' + Return data now contains just the output of the rsync command, instead + of a dictionary as returned from :py:func:`cmd.run_all + <salt.modules.cmdmod.run_all>`. + Rsync files from src to dst CLI Example: @@ -78,30 +84,27 @@ def rsync(src, exclude = __salt__['config.option']('rsync.exclude') if not excludefrom: excludefrom = __salt__['config.option']('rsync.excludefrom') + if not dryrun: + dryrun = __salt__['config.option']('rsync.dryrun') if not src or not dst: - raise CommandExecutionError('ERROR: src and dst cannot be empty.') - - option = _check(delete, force, update, passwordfile, exclude, excludefrom) - cmd = ( - r'''rsync {option} {src} {dst}''' - .format( - option=option, - src=src, - dst=dst, - ) - ) + raise SaltInvocationError('src and dst cannot be empty') + option = _check(delete, force, update, passwordfile, exclude, excludefrom, dryrun) + cmd = ['rsync'] + option + [src, dst] + log.debug('Running rsync command: {0}'.format(cmd)) try: - ret = __salt__['cmd.run_all'](cmd, python_shell=False) + return __salt__['cmd.run_all'](cmd, python_shell=False) except (IOError, OSError) as exc: raise CommandExecutionError(exc.strerror) - return ret - def version(): ''' - Return rsync version + Return data now contains just the version number as a string, instead + of a dictionary as returned from :py:func:`cmd.run_all + <salt.modules.cmdmod.run_all>`. + + Returns rsync version CLI Example: @@ -109,21 +112,28 @@ def version(): salt '*' rsync.version ''' - - cmd = (r'''rsync --version''') - try: - ret = __salt__['cmd.run_all'](cmd) + out = __salt__['cmd.run_stdout']( + ['rsync', '--version'], + python_shell=False) except (IOError, OSError) as exc: raise CommandExecutionError(exc.strerror) - - ret['stdout'] = ret['stdout'].split('\n')[0].split()[2] - return ret + try: + return out.split('\n')[0].split()[2] + except IndexError: + raise CommandExecutionError('Unable to determine rsync version') -def config(confile='/etc/rsyncd.conf'): +def config(conf_path='/etc/rsyncd.conf'): ''' - Return rsync config + Return data now contains just the contents of the rsyncd.conf as a + string, instead of a dictionary as returned from :py:func:`cmd.run_all + <salt.modules.cmdmod.run_all>`. + + Returns the contents of the rsync config file + + conf_path : /etc/rsyncd.conf + Path to the config file CLI Example: @@ -131,20 +141,25 @@ def config(confile='/etc/rsyncd.conf'): salt '*' rsync.config ''' - - if not os.path.isfile(confile): - raise CommandExecutionError('{0!r} does not exit'.format(confile)) - - cmd = ( - r'''cat {confile}''' - .format( - confile=confile - ) - ) - + ret = '' try: - ret = __salt__['cmd.run_all'](cmd, python_shell=False) - except (IOError, OSError) as exc: - raise CommandExecutionError(exc.strerror) - - return ret + with salt.utils.fopen(conf_path, 'r') as fp_: + for line in fp_: + ret += line + except IOError as exc: + if exc.errno == errno.ENOENT: + raise CommandExecutionError('{0} does not exist'.format(conf_path)) + elif exc.errno == errno.EACCES: + raise CommandExecutionError( + 'Unable to read {0}, access denied'.format(conf_path) + ) + elif exc.errno == errno.EISDIR: + raise CommandExecutionError( + 'Unable to read {0}, path is a directory'.format(conf_path) + ) + else: + raise CommandExecutionError( + 'Error {0}: {1}'.format(exc.errno, exc.strerror) + ) + else: + return ret diff --git a/salt/states/rsync.py b/salt/states/rsync.py new file mode 100644 index 0000000..7485da8 --- /dev/null +++ b/salt/states/rsync.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2015 SUSE LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +''' +State to synchronize files and directories with rsync. + +.. versionadded:: 2016.3.0 + +.. code-block:: yaml + + /opt/user-backups: + rsync.synchronized: + - source: /home + - force: True + +''' + +from __future__ import absolute_import +import salt.utils +import os + + +def __virtual__(): + ''' + Only if Rsync is available. + + :return: + ''' + return salt.utils.which('rsync') and 'rsync' or False + + +def _get_summary(rsync_out): + ''' + Get summary from the rsync successfull output. + ''' + + return "- " + "\n- ".join([elm for elm in rsync_out.split("\n\n")[-1].replace(" ", "\n").split("\n") if elm]) + + +def _get_changes(rsync_out): + ''' + Get changes from the rsync successfull output. + ''' + copied = list() + deleted = list() + + for line in rsync_out.split("\n\n")[0].split("\n")[1:]: + if line.startswith("deleting "): + deleted.append(line.split(" ", 1)[-1]) + else: + copied.append(line) + + return { + 'copied': os.linesep.join(sorted(copied)) or "N/A", + 'deleted': os.linesep.join(sorted(deleted)) or "N/A", + } + + +def synchronized(name, source, + delete=False, + force=False, + update=False, + passwordfile=None, + exclude=None, + excludefrom=None, + prepare=False, + dryrun=False): + ''' + Guarantees that the source directory is always copied to the target. + + name + Name of the target directory. + + source + Source directory. + + prepare + Create destination directory if it does not exists. + + delete + Delete extraneous files from the destination dirs (True or False) + + force + Force deletion of dirs even if not empty + + update + Skip files that are newer on the receiver (True or False) + + passwordfile + Read daemon-access password from the file (path) + + exclude + Exclude files, that matches pattern. + + excludefrom + Read exclude patterns from the file (path) + + dryrun + Perform a trial run with no changes made. Is the same as + doing test=True + + .. versionadded:: 2016.3.1 + ''' + + ret = {'name': name, 'changes': {}, 'result': True, 'comment': ''} + + if not os.path.exists(source): + ret['result'] = False + ret['comment'] = "Source directory {src} was not found.".format(src=source) + elif not os.path.exists(name) and not force and not prepare: + ret['result'] = False + ret['comment'] = "Destination directory {dest} was not found.".format(dest=name) + else: + if not os.path.exists(name) and prepare: + os.makedirs(name) + + if __opts__['test']: + dryrun = True + + result = __salt__['rsync.rsync'](source, name, delete=delete, force=force, update=update, + passwordfile=passwordfile, exclude=exclude, excludefrom=excludefrom, + dryrun=dryrun) + + if __opts__['test'] or dryrun: + ret['result'] = None + ret['comment'] = _get_summary(result['stdout']) + return ret + + if result.get('retcode'): + ret['result'] = False + ret['comment'] = result['stderr'] + ret['changes'] = result['stdout'] + else: + ret['comment'] = _get_summary(result['stdout']) + ret['changes'] = _get_changes(result['stdout']) + + return ret -- 2.10.2
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor