rsync module to add salt
Contents
In salt development version already has this function.
Salt version of me in my server used above is 2015.5.8 (Lithium), state module does not rsync, and you know it is crazy update all server to development version, So I ported this function to the current version. It’s very simple.
Add a custom module and state module,Copy the files from the development version over, only need a simple modification.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# 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 |
Update minion module.
1 2 3 |
# salt '*' saltutil.sync_modules # salt "*" saltutil.sync_states |
Sync file to minion:```
salt “*” state.sls sync/
1 2 3 4 5 6 7 8 9 10 11 |
My sls file:``` # cat /srv/salt/sync/init.sls sync_file: rsync.synchronized: - name: '/home/test/' - source: 'root@192.168.1.2::test' - delete: False - force: True - prepare: True - passwordfile: '/etc/rsyncd/rsyncd_secrets' |