1""" 2Wrapper for rsync 3 4.. versionadded:: 2014.1.0 5 6This data can also be passed into :ref:`pillar <pillar-walk-through>`. 7Options passed into opts will overwrite options passed into pillar. 8""" 9 10import errno 11import logging 12import re 13import tempfile 14 15import salt.utils.files 16import salt.utils.path 17from salt.exceptions import CommandExecutionError, SaltInvocationError 18 19log = logging.getLogger(__name__) 20 21__virtualname__ = "rsync" 22 23 24def __virtual__(): 25 """ 26 Only load module if rsync binary is present 27 """ 28 if salt.utils.path.which("rsync"): 29 return __virtualname__ 30 return ( 31 False, 32 "The rsync execution module cannot be loaded: " 33 "the rsync binary is not in the path.", 34 ) 35 36 37def _check(delete, force, update, passwordfile, exclude, excludefrom, dryrun, rsh): 38 """ 39 Generate rsync options 40 """ 41 options = ["-avz"] 42 43 if delete: 44 options.append("--delete") 45 if force: 46 options.append("--force") 47 if update: 48 options.append("--update") 49 if rsh: 50 options.append("--rsh={}".format(rsh)) 51 if passwordfile: 52 options.extend(["--password-file", passwordfile]) 53 if excludefrom: 54 options.extend(["--exclude-from", excludefrom]) 55 if exclude: 56 exclude = False 57 if exclude: 58 if isinstance(exclude, list): 59 for ex_ in exclude: 60 options.extend(["--exclude", ex_]) 61 else: 62 options.extend(["--exclude", exclude]) 63 if dryrun: 64 options.append("--dry-run") 65 return options 66 67 68def rsync( 69 src, 70 dst, 71 delete=False, 72 force=False, 73 update=False, 74 passwordfile=None, 75 exclude=None, 76 excludefrom=None, 77 dryrun=False, 78 rsh=None, 79 additional_opts=None, 80 saltenv="base", 81): 82 """ 83 .. versionchanged:: 2016.3.0 84 Return data now contains just the output of the rsync command, instead 85 of a dictionary as returned from :py:func:`cmd.run_all 86 <salt.modules.cmdmod.run_all>`. 87 88 Rsync files from src to dst 89 90 src 91 The source location where files will be rsynced from. 92 93 dst 94 The destination location where files will be rsynced to. 95 96 delete : False 97 Whether to enable the rsync `--delete` flag, which 98 will delete extraneous files from dest dirs 99 100 force : False 101 Whether to enable the rsync `--force` flag, which 102 will force deletion of dirs even if not empty. 103 104 update : False 105 Whether to enable the rsync `--update` flag, which 106 forces rsync to skip any files which exist on the 107 destination and have a modified time that is newer 108 than the source file. 109 110 passwordfile 111 A file that contains a password for accessing an 112 rsync daemon. The file should contain just the 113 password. 114 115 exclude 116 Whether to enable the rsync `--exclude` flag, which 117 will exclude files matching a PATTERN. 118 119 excludefrom 120 Whether to enable the rsync `--excludefrom` flag, which 121 will read exclude patterns from a file. 122 123 dryrun : False 124 Whether to enable the rsync `--dry-run` flag, which 125 will perform a trial run with no changes made. 126 127 rsh 128 Whether to enable the rsync `--rsh` flag, to 129 specify the remote shell to use. 130 131 additional_opts 132 Any additional rsync options, should be specified as a list. 133 134 saltenv 135 Specify a salt fileserver environment to be used. 136 137 CLI Example: 138 139 .. code-block:: bash 140 141 salt '*' rsync.rsync /path/to/src /path/to/dest delete=True update=True passwordfile=/etc/pass.crt exclude=exclude/dir 142 salt '*' rsync.rsync /path/to/src delete=True excludefrom=/xx.ini 143 salt '*' rsync.rsync /path/to/src delete=True exclude='[exclude1/dir,exclude2/dir]' additional_opts='["--partial", "--bwlimit=5000"]' 144 """ 145 if not src: 146 src = __salt__["config.option"]("rsync.src") 147 if not dst: 148 dst = __salt__["config.option"]("rsync.dst") 149 if not delete: 150 delete = __salt__["config.option"]("rsync.delete") 151 if not force: 152 force = __salt__["config.option"]("rsync.force") 153 if not update: 154 update = __salt__["config.option"]("rsync.update") 155 if not passwordfile: 156 passwordfile = __salt__["config.option"]("rsync.passwordfile") 157 if not exclude: 158 exclude = __salt__["config.option"]("rsync.exclude") 159 if not excludefrom: 160 excludefrom = __salt__["config.option"]("rsync.excludefrom") 161 if not dryrun: 162 dryrun = __salt__["config.option"]("rsync.dryrun") 163 if not rsh: 164 rsh = __salt__["config.option"]("rsync.rsh") 165 if not src or not dst: 166 raise SaltInvocationError("src and dst cannot be empty") 167 168 tmp_src = None 169 if src.startswith("salt://"): 170 _src = src 171 _path = re.sub("salt://", "", _src) 172 src_is_dir = False 173 if _path in __salt__["cp.list_master_dirs"](saltenv=saltenv): 174 src_is_dir = True 175 176 if src_is_dir: 177 tmp_src = tempfile.mkdtemp() 178 dir_src = __salt__["cp.get_dir"](_src, tmp_src, saltenv) 179 if dir_src: 180 src = tmp_src 181 # Ensure src ends in / so we 182 # get the contents not the tmpdir 183 # itself. 184 if not src.endswith("/"): 185 src = "{}/".format(src) 186 else: 187 raise CommandExecutionError("{} does not exist".format(src)) 188 else: 189 tmp_src = salt.utils.files.mkstemp() 190 file_src = __salt__["cp.get_file"](_src, tmp_src, saltenv) 191 if file_src: 192 src = tmp_src 193 else: 194 raise CommandExecutionError("{} does not exist".format(src)) 195 196 option = _check( 197 delete, force, update, passwordfile, exclude, excludefrom, dryrun, rsh 198 ) 199 200 if additional_opts and isinstance(additional_opts, list): 201 option = option + additional_opts 202 203 cmd = ["rsync"] + option + [src, dst] 204 log.debug("Running rsync command: %s", cmd) 205 try: 206 return __salt__["cmd.run_all"](cmd, python_shell=False) 207 except OSError as exc: 208 raise CommandExecutionError(exc.strerror) 209 finally: 210 if tmp_src: 211 __salt__["file.remove"](tmp_src) 212 213 214def version(): 215 """ 216 .. versionchanged:: 2016.3.0 217 Return data now contains just the version number as a string, instead 218 of a dictionary as returned from :py:func:`cmd.run_all 219 <salt.modules.cmdmod.run_all>`. 220 221 Returns rsync version 222 223 CLI Example: 224 225 .. code-block:: bash 226 227 salt '*' rsync.version 228 """ 229 try: 230 out = __salt__["cmd.run_stdout"](["rsync", "--version"], python_shell=False) 231 except OSError as exc: 232 raise CommandExecutionError(exc.strerror) 233 try: 234 return out.split("\n")[0].split()[2] 235 except IndexError: 236 raise CommandExecutionError("Unable to determine rsync version") 237 238 239def config(conf_path="/etc/rsyncd.conf"): 240 """ 241 .. versionchanged:: 2016.3.0 242 Return data now contains just the contents of the rsyncd.conf as a 243 string, instead of a dictionary as returned from :py:func:`cmd.run_all 244 <salt.modules.cmdmod.run_all>`. 245 246 Returns the contents of the rsync config file 247 248 conf_path : /etc/rsyncd.conf 249 Path to the config file 250 251 CLI Example: 252 253 .. code-block:: bash 254 255 salt '*' rsync.config 256 """ 257 ret = "" 258 try: 259 with salt.utils.files.fopen(conf_path, "r") as fp_: 260 for line in fp_: 261 ret += salt.utils.stringutils.to_unicode(line) 262 except OSError as exc: 263 if exc.errno == errno.ENOENT: 264 raise CommandExecutionError("{} does not exist".format(conf_path)) 265 elif exc.errno == errno.EACCES: 266 raise CommandExecutionError( 267 "Unable to read {}, access denied".format(conf_path) 268 ) 269 elif exc.errno == errno.EISDIR: 270 raise CommandExecutionError( 271 "Unable to read {}, path is a directory".format(conf_path) 272 ) 273 else: 274 raise CommandExecutionError("Error {}: {}".format(exc.errno, exc.strerror)) 275 else: 276 return ret 277