123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213 |
- """
- Useful non-core functionality, e.g. functions composing multiple operations.
- """
- from __future__ import with_statement
- from os import getcwd, sep
- import os.path
- from tempfile import mkdtemp
- from fabric.network import needs_host, key_filenames, normalize
- from fabric.operations import local, run, sudo, put
- from fabric.state import env, output
- from fabric.context_managers import cd
- __all__ = ['rsync_project', 'upload_project']
- @needs_host
- def rsync_project(
- remote_dir,
- local_dir=None,
- exclude=(),
- delete=False,
- extra_opts='',
- ssh_opts='',
- capture=False,
- upload=True,
- default_opts='-pthrvz'
- ):
- """
- Synchronize a remote directory with the current project directory via rsync.
- Where ``upload_project()`` makes use of ``scp`` to copy one's entire
- project every time it is invoked, ``rsync_project()`` uses the ``rsync``
- command-line utility, which only transfers files newer than those on the
- remote end.
- ``rsync_project()`` is thus a simple wrapper around ``rsync``; for
- details on how ``rsync`` works, please see its manpage. ``rsync`` must be
- installed on both your local and remote systems in order for this operation
- to work correctly.
- This function makes use of Fabric's ``local()`` operation, and returns the
- output of that function call; thus it will return the stdout, if any, of
- the resultant ``rsync`` call.
- ``rsync_project()`` uses the current Fabric connection parameters (user,
- host, port) by default, adding them to rsync's ssh options (then mixing in
- ``ssh_opts``, if given -- see below.)
- ``rsync_project()`` takes the following parameters:
- * ``remote_dir``: the only required parameter, this is the path to the
- directory on the remote server. Due to how ``rsync`` is implemented, the
- exact behavior depends on the value of ``local_dir``:
- * If ``local_dir`` ends with a trailing slash, the files will be
- dropped inside of ``remote_dir``. E.g.
- ``rsync_project("/home/username/project/", "foldername/")`` will drop
- the contents of ``foldername`` inside of ``/home/username/project``.
- * If ``local_dir`` does **not** end with a trailing slash (and this
- includes the default scenario, when ``local_dir`` is not specified),
- ``remote_dir`` is effectively the "parent" directory, and a new
- directory named after ``local_dir`` will be created inside of it. So
- ``rsync_project("/home/username", "foldername")`` would create a new
- directory ``/home/username/foldername`` (if needed) and place the
- files there.
- * ``local_dir``: by default, ``rsync_project`` uses your current working
- directory as the source directory. This may be overridden by specifying
- ``local_dir``, which is a string passed verbatim to ``rsync``, and thus
- may be a single directory (``"my_directory"``) or multiple directories
- (``"dir1 dir2"``). See the ``rsync`` documentation for details.
- * ``exclude``: optional, may be a single string, or an iterable of strings,
- and is used to pass one or more ``--exclude`` options to ``rsync``.
- * ``delete``: a boolean controlling whether ``rsync``'s ``--delete`` option
- is used. If True, instructs ``rsync`` to remove remote files that no
- longer exist locally. Defaults to False.
- * ``extra_opts``: an optional, arbitrary string which you may use to pass
- custom arguments or options to ``rsync``.
- * ``ssh_opts``: Like ``extra_opts`` but specifically for the SSH options
- string (rsync's ``--rsh`` flag.)
- * ``capture``: Sent directly into an inner `~fabric.operations.local` call.
- * ``upload``: a boolean controlling whether file synchronization is
- performed up or downstream. Upstream by default.
- * ``default_opts``: the default rsync options ``-pthrvz``, override if
- desired (e.g. to remove verbosity, etc).
- Furthermore, this function transparently honors Fabric's port and SSH key
- settings. Calling this function when the current host string contains a
- nonstandard port, or when ``env.key_filename`` is non-empty, will use the
- specified port and/or SSH key filename(s).
- For reference, the approximate ``rsync`` command-line call that is
- constructed by this function is the following::
- rsync [--delete] [--exclude exclude[0][, --exclude[1][, ...]]] \\
- [default_opts] [extra_opts] <local_dir> <host_string>:<remote_dir>
- .. versionadded:: 1.4.0
- The ``ssh_opts`` keyword argument.
- .. versionadded:: 1.4.1
- The ``capture`` keyword argument.
- .. versionadded:: 1.8.0
- The ``default_opts`` keyword argument.
- """
- # Turn single-string exclude into a one-item list for consistency
- if not hasattr(exclude, '__iter__'):
- exclude = (exclude,)
- # Create --exclude options from exclude list
- exclude_opts = ' --exclude "%s"' * len(exclude)
- # Double-backslash-escape
- exclusions = tuple([str(s).replace('"', '\\\\"') for s in exclude])
- # Honor SSH key(s)
- key_string = ""
- keys = key_filenames()
- if keys:
- key_string = "-i " + " -i ".join(keys)
- # Port
- user, host, port = normalize(env.host_string)
- port_string = "-p %s" % port
- # RSH
- rsh_string = ""
- if env.gateway is None:
- gateway_opts = ""
- else:
- gw_user, gw_host, gw_port = normalize(env.gateway)
- gw_str = "-A -o \"ProxyCommand=ssh %s -p %s %s@%s nc %s %s\""
- gateway_opts = gw_str % (
- key_string, gw_port, gw_user, gw_host, host, port
- )
- rsh_parts = [key_string, port_string, ssh_opts, gateway_opts]
- if any(rsh_parts):
- rsh_string = "--rsh='ssh %s'" % " ".join(rsh_parts)
- # Set up options part of string
- options_map = {
- 'delete': '--delete' if delete else '',
- 'exclude': exclude_opts % exclusions,
- 'rsh': rsh_string,
- 'default': default_opts,
- 'extra': extra_opts,
- }
- options = "%(delete)s%(exclude)s %(default)s %(extra)s %(rsh)s" % options_map
- # Get local directory
- if local_dir is None:
- local_dir = '../' + getcwd().split(sep)[-1]
- # Create and run final command string
- if host.count(':') > 1:
- # Square brackets are mandatory for IPv6 rsync address,
- # even if port number is not specified
- remote_prefix = "[%s@%s]" % (user, host)
- else:
- remote_prefix = "%s@%s" % (user, host)
- if upload:
- cmd = "rsync %s %s %s:%s" % (options, local_dir, remote_prefix, remote_dir)
- else:
- cmd = "rsync %s %s:%s %s" % (options, remote_prefix, remote_dir, local_dir)
- if output.running:
- print("[%s] rsync_project: %s" % (env.host_string, cmd))
- return local(cmd, capture=capture)
- def upload_project(local_dir=None, remote_dir="", use_sudo=False):
- """
- Upload the current project to a remote system via ``tar``/``gzip``.
- ``local_dir`` specifies the local project directory to upload, and defaults
- to the current working directory.
- ``remote_dir`` specifies the target directory to upload into (meaning that
- a copy of ``local_dir`` will appear as a subdirectory of ``remote_dir``)
- and defaults to the remote user's home directory.
- ``use_sudo`` specifies which method should be used when executing commands
- remotely. ``sudo`` will be used if use_sudo is True, otherwise ``run`` will
- be used.
- This function makes use of the ``tar`` and ``gzip`` programs/libraries,
- thus it will not work too well on Win32 systems unless one is using Cygwin
- or something similar. It will attempt to clean up the local and remote
- tarfiles when it finishes executing, even in the event of a failure.
- .. versionchanged:: 1.1
- Added the ``local_dir`` and ``remote_dir`` kwargs.
- .. versionchanged:: 1.7
- Added the ``use_sudo`` kwarg.
- """
- runner = use_sudo and sudo or run
- local_dir = local_dir or os.getcwd()
- # Remove final '/' in local_dir so that basename() works
- local_dir = local_dir.rstrip(os.sep)
- local_path, local_name = os.path.split(local_dir)
- local_path = local_path or '.'
- tar_file = "%s.tar.gz" % local_name
- target_tar = os.path.join(remote_dir, tar_file)
- tmp_folder = mkdtemp()
- try:
- tar_path = os.path.join(tmp_folder, tar_file)
- local("tar -czf %s -C %s %s" % (tar_path, local_path, local_name))
- put(tar_path, target_tar, use_sudo=use_sudo)
- with cd(remote_dir):
- try:
- runner("tar -xzf %s" % tar_file)
- finally:
- runner("rm -f %s" % tar_file)
- finally:
- local("rm -rf %s" % tmp_folder)
|