# mkdir /srv/salt/_modules
# mkdir /srv/salt/_states
# vim /srv/salt/_modules/rsync.py
# -*- coding: utf-8 -*-
'''
Wrapper for rsync
.. versionadded:: 2014.1.0
This data can also be passed into :doc:`pillar </topics/tutorials/pillar>`.
Options passed into opts will overwrite options passed into pillar.
'''
from __future__ import absolute_import
# Import python libs
import errno
import logging
# Import salt libs
import salt.utils
from salt.exceptions import CommandExecutionError, SaltInvocationError
log = logging.getLogger(__name__)
def _check(delete, force, update, passwordfile, exclude, excludefrom):
'''
Generate rsync options
'''
options = ['-avz']
if delete:
options.append('--delete')
if force:
options.append('--force')
if update:
options.append('--update')
if passwordfile:
options.extend(['--password-file', passwordfile])
if excludefrom:
options.extend(['--exclude-from', excludefrom])
if exclude:
exclude = False
if exclude:
options.extend(['--exclude', exclude])
return options
def rsync(src,
dst,
delete=False,
force=False,
update=False,
passwordfile=None,
exclude=None,
excludefrom=None):
'''
.. versionchanged:: Boron
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:
.. code-block:: bash
salt '*' rsync.rsync {src} {dst} {delete=True} {update=True} {passwordfile=/etc/pass.crt} {exclude=xx}
salt '*' rsync.rsync {src} {dst} {delete=True} {excludefrom=/xx.ini}
'''
if not src:
src = __salt__['config.option']('rsync.src')
if not dst:
dst = __salt__['config.option']('rsync.dst')
if not delete:
delete = __salt__['config.option']('rsync.delete')
if not force:
force = __salt__['config.option']('rsync.force')
if not update:
update = __salt__['config.option']('rsync.update')
if not passwordfile:
passwordfile = __salt__['config.option']('rsync.passwordfile')
if not exclude:
exclude = __salt__['config.option']('rsync.exclude')
if not excludefrom:
excludefrom = __salt__['config.option']('rsync.excludefrom')
if not src or not dst:
raise SaltInvocationError('src and dst cannot be empty')
option = _check(delete, force, update, passwordfile, exclude, excludefrom)
cmd = ['rsync'] + option + [src, dst]
try:
return __salt__['cmd.run_all'](cmd, python_shell=False)
except (IOError, OSError) as exc:
raise CommandExecutionError(exc.strerror)
def version():
'''
.. versionchanged:: Boron
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:
.. code-block:: bash
salt '*' rsync.version
'''
try:
out = __salt__['cmd.run_stdout'](
['rsync', '--version'],
python_shell=False)
except (IOError, OSError) as exc:
raise CommandExecutionError(exc.strerror)
try:
return out.split('n')[0].split()[2]
except IndexError:
raise CommandExecutionError('Unable to determine rsync version')
def config(conf_path='/etc/rsyncd.conf'):
'''
.. versionchanged:: Boron
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:
.. code-block:: bash
salt '*' rsync.config
'''
ret = ''
try:
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
# vim /srv/salt/_states/rsync.py
# -*- 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.
'''
Rsync state.
.. versionadded:: Boron
'''
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.
:param rsync_out:
:return:
'''
return "- " + "n- ".join([elm for elm in rsync_out.split("nn")[-1].replace(" ", "n").split("n") if elm])
def _get_changes(rsync_out):
'''
Get changes from the rsync successfull output.
:param rsync_out:
:return:
'''
copied = list()
deleted = list()
for line in rsync_out.split("nn")[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):
'''
Guarantees that the source directory is always copied to the target.
:param name: Name of the target directory.
:param source: Source directory.
:param prepare: Create destination directory if it does not exists.
:param delete: Delete extraneous files from the destination dirs (True or False)
:param force: Force deletion of dirs even if not empty
:param update: Skip files that are newer on the receiver (True or False)
:param passwordfile: Read daemon-access password from the file (path)
:param exclude: Exclude files, that matches pattern.
:param excludefrom: Read exclude patterns from the file (path)
:return:
.. code-block:: yaml
/opt/user-backups:
rsync.synchronized:
- source: /home
- force: True
'''
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:
if 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)
result = __salt__['rsync.rsync'](src=source, dst=name, delete=delete, force=force, update=update,
passwordfile=passwordfile, exclude=exclude, excludefrom=excludefrom)
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