Ejemplo n.º 1
0
    def put(self, local_path, remote_path, use_sudo, mirror_local_mode, mode,
            local_is_path, temp_dir):

        from rtox.fabric.api import sudo, hide
        pre = self.ftp.getcwd()
        pre = pre if pre else ''
        if local_is_path and self.isdir(remote_path):
            basename = os.path.basename(local_path)
            remote_path = posixpath.join(remote_path, basename)
        if output.running:
            print("[%s] put: %s -> %s" %
                  (env.host_string, _format_local(local_path, local_is_path),
                   posixpath.join(pre, remote_path)))
        # When using sudo, "bounce" the file through a guaranteed-unique file
        # path in the default remote CWD (which, typically, the login user will
        # have write permissions on) in order to sudo(mv) it later.
        if use_sudo:
            target_path = remote_path
            remote_path = posixpath.join(temp_dir, uuid.uuid4().hex)
        # Read, ensuring we handle file-like objects correct re: seek pointer
        putter = self.ftp.put
        if not local_is_path:
            old_pointer = local_path.tell()
            local_path.seek(0)
            putter = self.ftp.putfo
        rattrs = putter(local_path, remote_path)
        if not local_is_path:
            local_path.seek(old_pointer)
        # Handle modes if necessary
        if (local_is_path and mirror_local_mode) or (mode is not None):
            lmode = os.stat(local_path).st_mode if mirror_local_mode else mode
            # Cast to octal integer in case of string
            if isinstance(lmode, six.string_types):
                lmode = int(lmode, 8)
            lmode = lmode & int('0o7777', 8)
            rmode = rattrs.st_mode
            # Only bitshift if we actually got an rmode
            if rmode is not None:
                rmode = rmode & int('0o7777', 8)
            if lmode != rmode:
                if use_sudo:
                    # Temporarily nuke 'cwd' so sudo() doesn't "cd" its mv
                    # command. (The target path has already been cwd-ified
                    # elsewhere.)
                    with settings(hide('everything'), cwd=""):
                        sudo('chmod %o \"%s\"' % (lmode, remote_path))
                else:
                    self.ftp.chmod(remote_path, lmode)
        if use_sudo:
            # Temporarily nuke 'cwd' so sudo() doesn't "cd" its mv command.
            # (The target path has already been cwd-ified elsewhere.)
            with settings(hide('everything'), cwd=""):
                sudo("mv \"%s\" \"%s\"" % (remote_path, target_path))
            # Revert to original remote_path for return value's sake
            remote_path = target_path
        return remote_path
Ejemplo n.º 2
0
 def mkdir(self, path, use_sudo):
     from rtox.fabric.api import sudo, hide
     if use_sudo:
         with hide('everything'):
             sudo('mkdir "%s"' % path)
     else:
         self.ftp.mkdir(path)
Ejemplo n.º 3
0
 def local(self, command, silent=False, cwd=''):
     """Run the given command locally, echoing output."""
     with lcd(cwd):
         if silent:
             with hide('output', 'warnings'):
                 result = local(command)
         else:
             result = local(command)
     return result
Ejemplo n.º 4
0
def is_win():
    """
    Return True if remote SSH server is running Windows, False otherwise.

    The idea is based on echoing quoted text: \*NIX systems will echo quoted
    text only, while Windows echoes quotation marks as well.
    """
    with settings(hide('everything'), warn_only=True):
        return '"' in run('echo "Will you echo quotation marks"')
Ejemplo n.º 5
0
def contains(filename,
             text,
             exact=False,
             use_sudo=False,
             escape=True,
             shell=False,
             case_sensitive=True):
    """
    Return True if ``filename`` contains ``text`` (which may be a regex.)

    By default, this function will consider a partial line match (i.e. where
    ``text`` only makes up part of the line it's on). Specify ``exact=True`` to
    change this behavior so that only a line containing exactly ``text``
    results in a True return value.

    This function leverages ``egrep`` on the remote end (so it may not follow
    Python regular expression syntax perfectly), and skips ``env.shell``
    wrapper by default.

    If ``use_sudo`` is True, will use `sudo` instead of `run`.

    If ``escape`` is False, no extra regular expression related escaping is
    performed (this includes overriding ``exact`` so that no ``^``/``$`` is
    added.)

    The ``shell`` argument will be eventually passed to ``run/sudo``. See
    description of the same argument in ``~fabric.contrib.sed`` for details.

    If ``case_sensitive`` is False, the `-i` flag will be passed to ``egrep``.

    .. versionchanged:: 1.0
        Swapped the order of the ``filename`` and ``text`` arguments to be
        consistent with other functions in this module.
    .. versionchanged:: 1.4
        Updated the regular expression related escaping to try and solve
        various corner cases.
    .. versionchanged:: 1.4
        Added ``escape`` keyword argument.
    .. versionadded:: 1.6
        Added the ``shell`` keyword argument.
    .. versionadded:: 1.11
        Added the ``case_sensitive`` keyword argument.
    """
    func = use_sudo and sudo or run
    if escape:
        text = _escape_for_regex(text)
        if exact:
            text = "^%s$" % text
    with settings(hide('everything'), warn_only=True):
        egrep_cmd = 'egrep "%s" %s' % (text, _expand_path(filename))
        if not case_sensitive:
            egrep_cmd = egrep_cmd.replace('egrep', 'egrep -i', 1)
        return func(egrep_cmd, shell=shell).succeeded
Ejemplo n.º 6
0
def is_link(path, use_sudo=False, verbose=False):
    """
    Return True if the given path is a symlink on the current remote host.

    If ``use_sudo`` is True, will use `.sudo` instead of `.run`.

    `.is_link` will, by default, hide all output. Give ``verbose=True`` to
    change this.
    """
    func = sudo if use_sudo else run
    cmd = 'test -L "$(echo %s)"' % path
    args, kwargs = [], {'warn_only': True}
    if not verbose:
        args = [hide('everything')]
    with settings(*args, **kwargs):
        return func(cmd).succeeded
Ejemplo n.º 7
0
 def run(self, command, silent=False, cwd='', warn_only=False):
     """Run the given command remotely over SSH, echoing output locally."""
     env.warn_only = warn_only
     with settings(shell_env(**self.passenv)), cd(cwd):
         if silent:
             with hide('output', 'warnings'):
                 result = run(
                     command,
                     shell=True,
                     pty=False,  # for combine_stderr=False
                     combine_stderr=False,
                     shell_escape=False)
         else:
             result = run(
                 command,
                 shell=True,
                 pty=False,  # for combine_stderr=Falsek
                 combine_stderr=False,
                 shell_escape=False)
     return result
Ejemplo n.º 8
0
def exists(path, use_sudo=False, verbose=False):
    """
    Return True if given path exists on the current remote host.

    If ``use_sudo`` is True, will use `sudo` instead of `run`.

    `exists` will, by default, hide all output (including the run line, stdout,
    stderr and any warning resulting from the file not existing) in order to
    avoid cluttering output. You may specify ``verbose=True`` to change this
    behavior.

    .. versionchanged:: 1.13
        Replaced internal use of ``test -e`` with ``stat`` for improved remote
        cross-platform (e.g. Windows) compatibility.
    """
    func = use_sudo and sudo or run
    cmd = 'stat %s' % _expand_path(path)
    # If verbose, run normally
    if verbose:
        with settings(warn_only=True):
            return not func(cmd).failed
    # Otherwise, be quiet
    with settings(hide('everything'), warn_only=True):
        return not func(cmd).failed
Ejemplo n.º 9
0
def upload_template(filename,
                    destination,
                    context=None,
                    use_jinja=False,
                    template_dir=None,
                    use_sudo=False,
                    backup=True,
                    mirror_local_mode=False,
                    mode=None,
                    pty=None,
                    keep_trailing_newline=False,
                    temp_dir=''):
    """
    Render and upload a template text file to a remote host.

    Returns the result of the inner call to `~fabric.operations.put` -- see its
    documentation for details.

    ``filename`` should be the path to a text file, which may contain `Python
    string interpolation formatting
    <http://docs.python.org/library/stdtypes.html#string-formatting>`_ and will
    be rendered with the given context dictionary ``context`` (if given.)

    Alternately, if ``use_jinja`` is set to True and you have the Jinja2
    templating library available, Jinja will be used to render the template
    instead. Templates will be loaded from the invoking user's current working
    directory by default, or from ``template_dir`` if given.

    The resulting rendered file will be uploaded to the remote file path
    ``destination``.  If the destination file already exists, it will be
    renamed with a ``.bak`` extension unless ``backup=False`` is specified.

    By default, the file will be copied to ``destination`` as the logged-in
    user; specify ``use_sudo=True`` to use `sudo` instead.

    The ``mirror_local_mode``, ``mode``, and ``temp_dir`` kwargs are passed
    directly to an internal `~fabric.operations.put` call; please see its
    documentation for details on these two options.

    The ``pty`` kwarg will be passed verbatim to any internal
    `~fabric.operations.run`/`~fabric.operations.sudo` calls, such as those
    used for testing directory-ness, making backups, etc.

    The ``keep_trailing_newline`` kwarg will be passed when creating
    Jinja2 Environment which is False by default, same as Jinja2's
    behaviour.

    .. versionchanged:: 1.1
        Added the ``backup``, ``mirror_local_mode`` and ``mode`` kwargs.
    .. versionchanged:: 1.9
        Added the ``pty`` kwarg.
    .. versionchanged:: 1.11
        Added the ``keep_trailing_newline`` kwarg.
    .. versionchanged:: 1.11
        Added the  ``temp_dir`` kwarg.
    """
    func = use_sudo and sudo or run
    if pty is not None:
        func = partial(func, pty=pty)
    # Normalize destination to be an actual filename, due to using StringIO
    with settings(hide('everything'), warn_only=True):
        if func('test -d %s' % _expand_path(destination)).succeeded:
            sep = "" if destination.endswith('/') else "/"
            destination += sep + os.path.basename(filename)

    # Use mode kwarg to implement mirror_local_mode, again due to using
    # StringIO
    if mirror_local_mode and mode is None:
        mode = os.stat(apply_lcwd(filename, env)).st_mode
        # To prevent put() from trying to do this
        # logic itself
        mirror_local_mode = False

    # Process template
    text = None
    if use_jinja:
        try:
            template_dir = template_dir or os.getcwd()
            template_dir = apply_lcwd(template_dir, env)
            from jinja2 import Environment, FileSystemLoader
            jenv = Environment(loader=FileSystemLoader(template_dir),
                               keep_trailing_newline=keep_trailing_newline)
            text = jenv.get_template(filename).render(**context or {})
            # Force to a byte representation of Unicode, or str()ification
            # within Paramiko's SFTP machinery may cause decode issues for
            # truly non-ASCII characters.
            text = text.encode('utf-8')
        except ImportError:
            import traceback
            tb = traceback.format_exc()
            abort(tb + "\nUnable to import Jinja2 -- see above.")
    else:
        if template_dir:
            filename = os.path.join(template_dir, filename)
        filename = apply_lcwd(filename, env)
        with open(os.path.expanduser(filename)) as inputfile:
            text = inputfile.read()
        if context:
            text = text % context

    # Back up original file
    if backup and exists(destination):
        func("cp %s{,.bak}" % _expand_path(destination))

    if six.PY3 is True and isinstance(text, bytes):
        text = text.decode('utf-8')

    # Upload the file.
    return put(local_path=six.StringIO(text),
               remote_path=destination,
               use_sudo=use_sudo,
               mirror_local_mode=mirror_local_mode,
               mode=mode,
               temp_dir=temp_dir)
Ejemplo n.º 10
0
def sed(filename,
        before,
        after,
        limit='',
        use_sudo=False,
        backup='.bak',
        flags='',
        shell=False):
    """
    Run a search-and-replace on ``filename`` with given regex patterns.

    Equivalent to ``sed -i<backup> -r -e "/<limit>/ s/<before>/<after>/<flags>g"
    <filename>``. Setting ``backup`` to an empty string will, disable backup
    file creation.

    For convenience, ``before`` and ``after`` will automatically escape forward
    slashes, single quotes and parentheses for you, so you don't need to
    specify e.g.  ``http:\/\/foo\.com``, instead just using ``http://foo\.com``
    is fine.

    If ``use_sudo`` is True, will use `sudo` instead of `run`.

    The ``shell`` argument will be eventually passed to `run`/`sudo`. It
    defaults to False in order to avoid problems with many nested levels of
    quotes and backslashes. However, setting it to True may help when using
    ``~fabric.operations.cd`` to wrap explicit or implicit ``sudo`` calls.
    (``cd`` by it's nature is a shell built-in, not a standalone command, so it
    should be called within a shell.)

    Other options may be specified with sed-compatible regex flags -- for
    example, to make the search and replace case insensitive, specify
    ``flags="i"``. The ``g`` flag is always specified regardless, so you do not
    need to remember to include it when overriding this parameter.

    .. versionadded:: 1.1
        The ``flags`` parameter.
    .. versionadded:: 1.6
        Added the ``shell`` keyword argument.
    """
    func = use_sudo and sudo or run
    # Characters to be escaped in both
    for char in "/'":
        before = before.replace(char, r'\%s' % char)
        after = after.replace(char, r'\%s' % char)
    # Characters to be escaped in replacement only (they're useful in regexen
    # in the 'before' part)
    for char in "()":
        after = after.replace(char, r'\%s' % char)
    if limit:
        limit = r'/%s/ ' % limit
    context = {
        'script': r"'%ss/%s/%s/%sg'" % (limit, before, after, flags),
        'filename': _expand_path(filename),
        'backup': backup
    }
    # Test the OS because of differences between sed versions

    with hide('running', 'stdout'):
        platform = run("uname", shell=False, pty=False)
    if platform in ('NetBSD', 'OpenBSD', 'QNX'):
        # Attempt to protect against failures/collisions
        hasher = hashlib.sha1()
        hasher.update(env.host_string)
        hasher.update(filename)
        context['tmp'] = "/tmp/%s" % hasher.hexdigest()
        # Use temp file to work around lack of -i
        expr = r"""cp -p %(filename)s %(tmp)s \
&& sed -r -e %(script)s %(filename)s > %(tmp)s \
&& cp -p %(filename)s %(filename)s%(backup)s \
&& mv %(tmp)s %(filename)s"""
    else:
        context['extended_regex'] = '-E' if platform == 'Darwin' else '-r'
        expr = r"sed -i%(backup)s %(extended_regex)s -e %(script)s %(filename)s"
    command = expr % context
    return func(command, shell=shell)
Ejemplo n.º 11
0
    def get(self,
            remote_path,
            local_path,
            use_sudo,
            local_is_path,
            rremote=None,
            temp_dir=""):
        from rtox.fabric.api import sudo, hide

        # rremote => relative remote path, so get(/var/log) would result in
        # this function being called with
        # remote_path=/var/log/apache2/access.log and
        # rremote=apache2/access.log
        rremote = rremote if rremote is not None else remote_path
        # Handle format string interpolation (e.g. %(dirname)s)
        path_vars = {
            'host': env.host_string.replace(':', '-'),
            'basename': os.path.basename(rremote),
            'dirname': os.path.dirname(rremote),
            'path': rremote
        }

        if local_is_path:
            # Fix for issue #711 and #1348 - escape %'s as well as possible.
            format_re = r'(%%(?!\((?:%s)\)\w))' % '|'.join(path_vars.keys())
            escaped_path = re.sub(format_re, r'%\1', local_path)
            local_path = os.path.abspath(escaped_path % path_vars)

            # Ensure we give ssh.SFTPCLient a file by prepending and/or
            # creating local directories as appropriate.
            dirpath, filepath = os.path.split(local_path)
            if dirpath and not os.path.exists(dirpath):
                os.makedirs(dirpath)
            if os.path.isdir(local_path):
                local_path = os.path.join(local_path, path_vars['basename'])

        if output.running:
            print("[%s] download: %s <- %s" %
                  (env.host_string, _format_local(local_path,
                                                  local_is_path), remote_path))
        # Warn about overwrites, but keep going
        if local_is_path and os.path.exists(local_path):
            msg = "Local file %s already exists and is being overwritten."
            warn(msg % local_path)

        # When using sudo, "bounce" the file through a guaranteed-unique file
        # path in the default remote CWD (which, typically, the login user will
        # have write permissions on) in order to sudo(cp) it.
        if use_sudo:
            target_path = posixpath.join(temp_dir, uuid.uuid4().hex)
            # Temporarily nuke 'cwd' so sudo() doesn't "cd" its mv command.
            # (The target path has already been cwd-ified elsewhere.)
            with settings(hide('everything'), cwd=""):
                sudo('cp -p "%s" "%s"' % (remote_path, target_path))
                # The user should always own the copied file.
                sudo('chown %s "%s"' % (env.user, target_path))
                # Only root and the user has the right to read the file
                sudo('chmod 400 "%s"' % target_path)
                remote_path = target_path

        try:
            # File-like objects: reset to file seek 0 (to ensure full overwrite)
            # and then use Paramiko's getfo() directly
            getter = self.ftp.get
            if not local_is_path:
                local_path.seek(0)
                getter = self.ftp.getfo
            getter(remote_path, local_path)
        finally:
            # try to remove the temporary file after the download
            if use_sudo:
                with settings(hide('everything'), cwd=""):
                    sudo('rm -f "%s"' % remote_path)

        # Return local_path object for posterity. (If mutated, caller will want
        # to know.)
        return local_path