Merge pull request #35605 from rallytime/bp-32739

Back-port #32739 to 2016.3
This commit is contained in:
Mike Place 2016-08-20 10:39:38 +09:00 committed by GitHub
commit 4153aeba29
3 changed files with 64 additions and 29 deletions

View file

@ -20,7 +20,7 @@ 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
'''
@ -40,6 +40,8 @@ def _check(delete, force, update, passwordfile, exclude, excludefrom):
exclude = False
if exclude:
options.extend(['--exclude', exclude])
if dryrun:
options.append('--dry-run')
return options
@ -50,7 +52,8 @@ def rsync(src,
update=False,
passwordfile=None,
exclude=None,
excludefrom=None):
excludefrom=None,
dryrun=False):
'''
.. versionchanged:: 2016.3.0
Return data now contains just the output of the rsync command, instead
@ -82,13 +85,16 @@ 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 SaltInvocationError('src and dst cannot be empty')
option = _check(delete, force, update, passwordfile, exclude, excludefrom)
option = _check(delete, force, update, passwordfile, exclude, excludefrom, dryrun)
cmd = ['rsync'] + option + [src, dst]
log.debug('Running rsync command: {0}'.format(cmd))
try:
return __salt__['cmd.run'](cmd, python_shell=False)
return __salt__['cmd.run_all'](cmd, python_shell=False)
except (IOError, OSError) as exc:
raise CommandExecutionError(exc.strerror)

View file

@ -14,9 +14,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
'''
Rsync state.
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
@ -36,9 +44,6 @@ def __virtual__():
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("\n\n")[-1].replace(" ", "\n").split("\n") if elm])
@ -47,9 +52,6 @@ def _get_summary(rsync_out):
def _get_changes(rsync_out):
'''
Get changes from the rsync successfull output.
:param rsync_out:
:return:
'''
copied = list()
deleted = list()
@ -73,28 +75,45 @@ def synchronized(name, source,
passwordfile=None,
exclude=None,
excludefrom=None,
prepare=False):
prepare=False,
dryrun=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:
name
Name of the target directory.
.. code-block:: yaml
source
Source directory.
/opt/user-backups:
rsync.synchronized:
- source: /home
- force: True
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):
@ -107,8 +126,18 @@ def synchronized(name, source,
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)
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']

View file

@ -40,7 +40,7 @@ class RsyncTestCase(TestCase):
with patch.dict(rsync.__salt__,
{'config.option': MagicMock(return_value='A'),
'cmd.run': MagicMock(side_effect=[IOError('f'),
'cmd.run_all': MagicMock(side_effect=[IOError('f'),
'A'])}):
with patch.object(rsync, '_check', return_value=['A']):
self.assertRaises(CommandExecutionError, rsync.rsync, 'a', 'b')