Example #1
0
def _expand_remote_dest(local_path, remote_path):
    if remote_path is None:
        if local_path is None:
            raise RuntimeError('one of local_path, remote_path is required')
        remote_path = local_path

    st = remote.lstat(remote_path)
    if st:
        # file exists, check if it is a link
        if S_ISLNK(st.st_mode):
            # normalize (dangling links will raise an exception)
            remote_path = remote.normalize(remote_path)

            # update stat
            st = remote.lstat(remote_path)

        # dir-expansion, since st is guaranteed not be a link
        if st and S_ISDIR(st.st_mode):
            if local_path is None:
                raise RemoteFailureError('Is a directory: {}'.format(
                    remote_path))

            # if it's a directory, correct path
            remote_path = remote.path.join(remote_path,
                                           remote.path.basename(local_path))

            st = remote.lstat(remote_path)
            log.debug('Expanded remote_path to {!r}'.format(remote_path))

    # ensure st is either non-existant, or a regular file
    if st and not S_ISREG(st.st_mode):
        raise RemoteFailureError('Not a regular file: {!r}'.format(
            remote_path))
    return st, remote_path
Example #2
0
File: ssl.py Project: mbr/remand
def ensure_certificate(hostname):
    cert_rpath = remote.path.join(config['sslcert_cert_dir'],
                                  hostname + '.crt')
    chain_rpath = remote.path.join(config['sslcert_cert_dir'],
                                   hostname + '.chain.crt')
    key_rpath = remote.path.join(config['sslcert_key_dir'], hostname + '.pem')

    # first, ensure any certificate exists on the host. otherwise,
    # webservers like nginx will likely not start
    if not (remote.lstat(cert_rpath) and remote.lstat(key_rpath)
            and remote.lstat(chain_rpath)):
        log.debug('Remote certificate {}, key {}, chain {} not found'.format(
            cert_rpath, key_rpath, chain_rpath))

        key, cert = generate_self_signed_cert(hostname)

        # FIXME: maybe use install cert here.
        fs.upload_string(key, key_rpath)
        fs.upload_string(cert, cert_rpath)
        fs.upload_string(cert, chain_rpath)

        return Changed(
            msg='No certificate {} / key {} / chain {} found. A self-signed '
            'certficate from a reputable snake-oil vendor was installed.'.
            format(cert_rpath, key_rpath, chain_rpath))

    return Unchanged(
        'Certificate for hostname {} already preset'.format(hostname))
Example #3
0
def walk(top, topdown=True, onerror=None, followlinks=False):
    try:
        names = remote.listdir(top)
    except OSError as e:
        if onerror:
            onerror(e)
        return

    dirs, files = [], []
    for name in names:
        fn = remote.path.join(top, name)
        st = remote.lstat(fn)

        if S_ISDIR(st.st_mode):
            dirs.append(name)
        else:
            files.append(name)

    if topdown:
        yield top, dirs, files

    for name in dirs:
        fn = remote.path.join(top, name)
        st = remote.lstat(fn)

        if followlinks or not S_ISLNK(st.st_mode):
            for rv in walk(fn, topdown, onerror, followlinks):
                yield rv

    if not topdown:
        yield top, dirs, files
Example #4
0
def _expand_remote_dest(local_path, remote_path):
    if remote_path is None:
        if local_path is None:
            raise RuntimeError('one of local_path, remote_path is required')
        remote_path = local_path

    st = remote.lstat(remote_path)
    if st:
        # file exists, check if it is a link
        if S_ISLNK(st.st_mode):
            # normalize (dangling links will raise an exception)
            remote_path = remote.normalize(remote_path)

            # update stat
            st = remote.lstat(remote_path)

        # dir-expansion, since st is guaranteed not be a link
        if st and S_ISDIR(st.st_mode):
            if local_path is None:
                raise RemoteFailureError(
                    'Is a directory: {}'.format(remote_path))

            # if it's a directory, correct path
            remote_path = remote.path.join(remote_path,
                                           remote.path.basename(local_path))

            st = remote.lstat(remote_path)
            log.debug('Expanded remote_path to {!r}'.format(remote_path))

    # ensure st is either non-existant, or a regular file
    if st and not S_ISREG(st.st_mode):
        raise RemoteFailureError(
            'Not a regular file: {!r}'.format(remote_path))
    return st, remote_path
Example #5
0
def walk(top, topdown=True, onerror=None, followlinks=False):
    try:
        names = remote.listdir(top)
    except OSError as e:
        if onerror:
            onerror(e)
        return

    dirs, files = [], []
    for name in names:
        fn = remote.path.join(top, name)
        st = remote.lstat(fn)

        if S_ISDIR(st.st_mode):
            dirs.append(name)
        else:
            files.append(name)

    if topdown:
        yield top, dirs, files

    for name in dirs:
        fn = remote.path.join(top, name)
        st = remote.lstat(fn)

        if followlinks or not S_ISLNK(st.st_mode):
            for rv in walk(fn, topdown, onerror, followlinks):
                yield rv

    if not topdown:
        yield top, dirs, files
Example #6
0
File: ssl.py Project: mbr/remand
def install_cert(cert, key, cert_name=None, key_name=None):
    cert_name = cert_name or os.path.basename(cert)
    key_name = key_name or os.path.basename(key)

    # small sanity check
    with open(cert) as f:
        if 'PRIVATE' in f.read():
            raise ValueError(
                'You seem to have passed a private key as a cert!')

    with open(key) as f:
        if 'PRIVATE' not in f.read():
            raise ValueError(
                '{} does not seem to be a valid private key'.format(key))

    # check if remote is reasonably secure
    cert_dir = config['sslcert_cert_dir']
    cert_dir_st = remote.lstat(cert_dir)

    if not cert_dir_st:
        raise ConfigurationError('Remote SSL dir {} does not exist'.format(
            cert_dir))

    key_dir = config['sslcert_key_dir']
    key_dir_st = remote.lstat(key_dir)

    if not key_dir_st:
        raise ConfigurationError('Remote key dir {} does not exist'.format(
            key_dir))

    SECURE_MODES = (0o700, 0o710)
    actual_mode = key_dir_st.st_mode & 0o777
    if actual_mode not in SECURE_MODES:
        raise ConfigurationError(
            'Mode of remote key dir {} is {:o}, should be one of {:o}'.format(
                key_dir, actual_mode, SECURE_MODES))

    if key_dir_st.st_uid != 0:
        raise ConfigurationError(
            'Remove key dir {} is not owned by root'.format(key_dir))

    # we can safely upload the key and cert
    cert_rpath = remote.path.join(cert_dir, cert_name)
    key_rpath = remote.path.join(key_dir, key_name)

    changed = False
    changed |= fs.upload_file(cert, cert_rpath).changed
    changed |= fs.upload_file(key, key_rpath).changed
    changed |= fs.chmod(key_rpath, 0o600).changed

    if changed:
        return Changed(
            msg='Uploaded key pair {}/{}'.format(cert_name, key_name))

    return Unchanged(
        msg='Key pair {}/{} already uploaded'.format(cert_name, key_name))
Example #7
0
def symlink(src, dst):
    if dst.endswith('/'):
        raise NotImplementedError('Creating link inside directory not '
                                  'implemented')
    lst = remote.lstat(dst)

    if lst:
        if not S_ISLNK(lst.st_mode):
            raise RemotePathIsNotALinkError('Already exists and not a link: '
                                            '{}'.format(dst))

        # remote is a link
        rsrc = remote.readlink(dst)
        if rsrc == src:
            return Unchanged(msg='Unchanged link: {} -> {}'.format(dst, src))

        # we need to update the link, unfortunately, this is often not possible
        # atomically
        remote.unlink(dst)
        remote.symlink(src, dst)
        return Changed(msg='Changed link: {} -> {} (previously -> {})'
                       .format(dst, src, rsrc))

    remote.symlink(src, dst)
    return Changed(msg='Created link: {} -> {}'.format(dst, src))
Example #8
0
def remove_dir(remote_path, recursive=True):
    """Removes a remote directory.

    If the directory does not exist, does nothing.

    :param recursive: Makes ``remove_dir`` behave closer to ``rm -rf`` instead
                      of ``rmdir``.
    """
    st = remote.lstat(remote_path)

    if st is None:
        return Unchanged(msg=u'Directory already gone: {}'.format(remote_path))

    # if it is not a directory, don't touch it
    if not S_ISDIR(st.st_mode):
        raise RemotePathIsNotADirectoryError(remote_path)

    if recursive:
        for dirpath, dirnames, filenames in walk(remote_path, topdown=False):
            for fn in filenames:
                remote.unlink(remote.path.join(dirpath, fn))
            remote.rmdir(dirpath)
    else:
        remote.rmdir(dirpath)

    return Changed(msg=u'Removed directory: {}'.format(remote_path))
Example #9
0
def init_authorized_keys(user='******', fix_permissions=True):
    ak_file = get_authorized_keys_file(user)
    ak_dir = remote.path.dirname(ak_file)

    changed = False

    # ensure the directory exists
    changed |= fs.create_dir(ak_dir, mode=AK_DIR_PERMS).changed

    if fix_permissions:
        changed |= fs.chmod(ak_dir, AK_DIR_PERMS).changed
        changed |= fs.chown(ak_dir, uid=user).changed

    # check if the authorized keys file exists
    if not remote.lstat(ak_file):
        changed |= fs.touch(ak_file).changed

    if fix_permissions:
        changed |= fs.chmod(ak_file, AK_FILE_PERMS).changed
        changed |= fs.chown(ak_dir, uid=user).changed

    # at this point, we have fixed permissions for file and dir, as well as
    # ensured they exist. however, they might still be owned by root

    if changed:
        return Changed(ak_file,
                       msg='Changed permissions or owner on authorized keys')
    return Unchanged(
        ak_file, msg='authorized keys file has correct owner and permissions')
Example #10
0
def touch(remote_path, mtime=None, atime=None):
    """Update mtime and atime of a path.

    Similar to running ``touch remote_path``.

    :param remote_path: Remote path whose times will get updated.
    :param mtime: New mtime. If ``None``, uses the current time.
    :param atime: New atime. Only used if ``mtime`` is not None. Defaults to
                  ``mtime``.
    :return: Since it always updates the current time, calling this function
             will always result in a modification.
    """
    # ensure the file exists
    if not remote.lstat(remote_path):
        with remote.file(remote_path, 'w') as out:
            out.write('')

    if mtime is None:
        remote.utime(remote_path, None)
        return Changed(msg=u'Touched {} to current time'.format(remote_path))
    else:
        atime = atime if atime is not None else mtime
        remote.utime(remote_path, (atime, mtime))
        return Changed(msg=u'Touched {} to mtime={}, atime={}'.format(
            remote_path, mtime, atime))
Example #11
0
File: ssh.py Project: mbr/remand
def init_authorized_keys(user='******', fix_permissions=True):
    ak_file = get_authorized_keys_file(user)
    ak_dir = remote.path.dirname(ak_file)

    changed = False

    # ensure the directory exists
    changed |= fs.create_dir(ak_dir, mode=AK_DIR_PERMS).changed

    if fix_permissions:
        changed |= fs.chmod(ak_dir, AK_DIR_PERMS).changed

    # check if the authorized keys file exists
    if not remote.lstat(ak_file):
        changed |= fs.touch(ak_file).changed

    if fix_permissions:
        changed |= fs.chmod(ak_file, AK_FILE_PERMS).changed

    # at this point, we have fixed permissions for file and dir, as well as
    # ensured they exist. however, they might still be owned by root

    if changed:
        return Changed(ak_file,
                       msg='Changed permissions or owner on authorized keys')
    return Unchanged(
        ak_file,
        msg='authorized keys file has correct owner and permissions')
Example #12
0
def touch(remote_path, mtime=None, atime=None):
    """Update mtime and atime of a path.

    Similar to running ``touch remote_path``.

    :param remote_path: Remote path whose times will get updated.
    :param mtime: New mtime. If ``None``, uses the current time.
    :param atime: New atime. Only used if ``mtime`` is not None. Defaults to
                  ``mtime``.
    :return: Since it always updates the current time, calling this function
             will always result in a modification.
    """
    # ensure the file exists
    if not remote.lstat(remote_path):
        with remote.file(remote_path, 'w') as out:
            out.write('')

    if mtime is None:
        remote.utime(remote_path, None)
        return Changed(msg=u'Touched {} to current time'.format(remote_path))
    else:
        atime = atime if atime is not None else mtime
        remote.utime(remote_path, (atime, mtime))
        return Changed(msg=u'Touched {} to mtime={}, atime={}'.format(
            remote_path, mtime, atime))
Example #13
0
def chmod(remote_path, mode, recursive=False, executable=False):
    # FIXME: instead of executable, add parsing of rwxX-style modes
    # FIXME: add speedup by using local chmod
    xmode = mode if not executable else mode | 0o111

    st = remote.lstat(remote_path)

    if mode > 0o777:
        raise ValueError('Modes above 0o777 are not supported')

    changed = False
    actual_mode = st.st_mode & 0o777

    # if the target is a directory or already has at least one executable bit,
    # we apply the executable mode (see chmod manpage for details)
    correct_mode = (xmode if S_ISDIR(st.st_mode) or actual_mode & 0o111 else
                    mode)

    if actual_mode != correct_mode:
        remote.chmod(remote_path, correct_mode)
        changed = True

    if recursive and S_ISDIR(st.st_mode):
        for rfn in remote.listdir(remote_path):
            changed |= chmod(
                remote.path.join(remote_path, rfn), mode, True,
                executable).changed

    if changed:
        return Changed(
            msg='Changed mode of {} to {:o}'.format(remote_path, mode))

    return Unchanged(msg='Mode of {} already {:o}'.format(remote_path, mode))
Example #14
0
def chmod(remote_path, mode, recursive=False, executable=False):
    # FIXME: instead of executable, add parsing of rwxX-style modes
    # FIXME: add speedup by using local chmod
    xmode = mode if not executable else mode | 0o111

    st = remote.lstat(remote_path)

    if mode > 0o777:
        raise ValueError('Modes above 0o777 are not supported')

    changed = False
    actual_mode = st.st_mode & 0o777

    # if the target is a directory or already has at least one executable bit,
    # we apply the executable mode (see chmod manpage for details)
    correct_mode = (xmode
                    if S_ISDIR(st.st_mode) or actual_mode & 0o111 else mode)

    if actual_mode != correct_mode:
        remote.chmod(remote_path, correct_mode)
        changed = True

    if recursive and S_ISDIR(st.st_mode):
        for rfn in remote.listdir(remote_path):
            changed |= chmod(
                remote.path.join(remote_path, rfn), mode, True,
                executable).changed

    if changed:
        return Changed(msg='Changed mode of {} to {:o}'.format(
            remote_path, mode))

    return Unchanged(msg='Mode of {} already {:o}'.format(remote_path, mode))
Example #15
0
def remove_dir(remote_path, recursive=True):
    """Removes a remote directory.

    If the directory does not exist, does nothing.

    :param recursive: Makes ``remove_dir`` behave closer to ``rm -rf`` instead
                      of ``rmdir``.
    """
    st = remote.lstat(remote_path)

    if st is None:
        return Unchanged(msg=u'Directory already gone: {}'.format(remote_path))

    # if it is not a directory, don't touch it
    if not S_ISDIR(st.st_mode):
        raise RemotePathIsNotADirectoryError(remote_path)

    if recursive:
        for dirpath, dirnames, filenames in walk(remote_path, topdown=False):
            for fn in filenames:
                remote.unlink(remote.path.join(dirpath, fn))
            remote.rmdir(dirpath)
    else:
        remote.rmdir(dirpath)

    return Changed(msg=u'Removed directory: {}'.format(remote_path))
Example #16
0
def symlink(src, dst):
    if dst.endswith('/'):
        raise NotImplementedError('Creating link inside directory not '
                                  'implemented')
    lst = remote.lstat(dst)

    if lst:
        if not S_ISLNK(lst.st_mode):
            raise RemotePathIsNotALinkError('Already exists and not a link: '
                                            '{}'.format(dst))

        # remote is a link
        rsrc = remote.readlink(dst)
        if rsrc == src:
            return Unchanged(msg='Unchanged link: {} -> {}'.format(dst, src))

        # we need to update the link, unfortunately, this is often not possible
        # atomically
        remote.unlink(dst)
        remote.symlink(src, dst)
        return Changed(msg='Changed link: {} -> {} (previously -> {})'.format(
            dst, src, rsrc))

    remote.symlink(src, dst)
    return Changed(msg='Created link: {} -> {}'.format(dst, src))
Example #17
0
File: ssh.py Project: mbr/remand
def regenerate_host_keys(mark='/etc/ssh/host_keys_regenerated'):
    if mark:
        if remote.lstat(mark):
            return Unchanged(msg='Hostkeys have already been regenerated')

    key_names = [
        '/etc/ssh/ssh_host_ecdsa_key',
        '/etc/ssh/ssh_host_ed25519_key',
        '/etc/ssh/ssh_host_rsa_key',
        '/etc/ssh/ssh_host_dsa_key',
    ]

    def collect_fingerprints():
        fps = ''
        for key in key_names:
            if remote.lstat(key):
                fps += proc.run(['ssh-keygen', '-l', '-f', key])[0]
        return fps

    old_fps = collect_fingerprints()

    # remove old keys
    for key in key_names:
        fs.remove_file(key)
        fs.remove_file(key + '.pub')

    # generate new ones
    proc.run(['dpkg-reconfigure', 'openssh-server'])

    # restart openssh
    systemd.restart_unit('ssh.service')

    new_fps = collect_fingerprints()

    # mark host keys as new
    fs.touch(mark)

    return Changed(
        msg='Regenerated SSH host keys.\n'
        'Old fingerprints:\n{}\nNew fingerprints:\n{}\n'.format(
            util.indent('    ', old_fps), util.indent('    ', new_fps)))
Example #18
0
def regenerate_host_keys(mark='/etc/ssh/host_keys_regenerated'):
    if mark:
        if remote.lstat(mark):
            return Unchanged(msg='Hostkeys have already been regenerated')

    key_names = [
        '/etc/ssh/ssh_host_ecdsa_key',
        '/etc/ssh/ssh_host_ed25519_key',
        '/etc/ssh/ssh_host_rsa_key',
        '/etc/ssh/ssh_host_dsa_key',
    ]

    def collect_fingerprints():
        fps = ''
        for key in key_names:
            if remote.lstat(key):
                fps += proc.run(['ssh-keygen', '-l', '-f', key])[0]
        return fps

    old_fps = collect_fingerprints()

    # remove old keys
    for key in key_names:
        fs.remove_file(key)
        fs.remove_file(key + '.pub')

    # generate new ones
    proc.run(['dpkg-reconfigure', 'openssh-server'])

    # restart openssh
    systemd.restart_unit('ssh.service')

    new_fps = collect_fingerprints()

    # mark host keys as new
    fs.touch(mark)

    return Changed(
        msg='Regenerated SSH host keys.\n'
        'Old fingerprints:\n{}\nNew fingerprints:\n{}\n'.format(
            util.indent('    ', old_fps), util.indent('    ', new_fps)))
Example #19
0
def edit(remote_path, create=True):
    with volatile.file() as tmp:
        created = False
        if create and not remote.lstat(remote_path):
            tmp.write('')
            created = True
        else:
            tmp.write(remote.file(remote_path, 'rb').read())
        tmp.close()

        try:
            ef = EditableFile(tmp.name)
            yield ef
        except Exception:
            raise
        else:
            if created or ef.modified:
                upload_file(ef.name, remote_path).changed
                ef.changed = True
            else:
                ef.changed = False
Example #20
0
def edit(remote_path, create=True):
    with volatile.file() as tmp:
        created = False
        if create and not remote.lstat(remote_path):
            tmp.write('')
            created = True
        else:
            tmp.write(remote.file(remote_path, 'rb').read())
        tmp.close()

        try:
            ef = EditableFile(tmp.name)
            yield ef
        except Exception:
            raise
        else:
            if created or ef.modified:
                upload_file(ef.name, remote_path).changed
                ef.changed = True
            else:
                ef.changed = False
Example #21
0
File: ssh.py Project: mbr/remand
 def collect_fingerprints():
     fps = ''
     for key in key_names:
         if remote.lstat(key):
             fps += proc.run(['ssh-keygen', '-l', '-f', key])[0]
     return fps
Example #22
0
def run():
    log.warning('Running testing module. Do not run this on a real machine!')

    log.debug('Testing popen')
    proc = remote.popen(['uname'])
    stdout, stderr = proc.communicate()
    assert 'Linux' == stdout.strip()

    log.debug('Testing getcwd()')
    assert '/home/vagrant' == remote.getcwd()

    log.debug('Testing chdir()')
    remote.chdir('/')
    assert '/' == remote.getcwd()
    remote.chdir('/home/vagrant')

    # create a sample file
    TESTFN = 'testfile'
    TESTDN = 'TESTDIR'
    log.debug('Testing file')
    with remote.file(TESTFN, mode='w') as out:
        out.write('test')

    log.debug('Testing chmod')
    remote.chmod(TESTFN, 0732)

    log.debug('Testing mkdir')
    # FIXME: umask?
    # FIXME: on exists/conflict?
    remote.mkdir(TESTDN, 0700)

    log.debug('Testing listdir')
    assert TESTFN in remote.listdir('.')
    assert TESTDN in remote.listdir('.')

    log.debug('Testing rmdir')
    remote.rmdir(TESTDN)

    # FIXME: can't test chown without root access

    log.debug('Testing normalize')
    assert '/home' == remote.normalize('./..')

    log.debug('Testing symlink')
    remote.symlink('to', 'from')

    log.debug('Testing lstat')
    remote.lstat('from')

    log.debug('Testing readlink')
    assert remote.readlink('/home/vagrant/from') == 'to'

    log.debug('Testing rename')
    remote.rename('from', 'from2')
    assert remote.readlink('/home/vagrant/from2') == 'to'

    log.debug('Testing unlink')
    remote.unlink('/home/vagrant/from2')

    log.debug('Testing stat')
    s = remote.stat(TESTFN)
    assert s.st_uid == 1000
    assert s.st_gid == 1000
    remote.unlink(TESTFN)
Example #23
0
File: ssl.py Project: mbr/remand
def install_cert(cert, key, cert_name=None, key_name=None):
    """Installs an SSL certificate with including key on the remote

    Certificate filenames are unchanged, per default they will be installed in
    `/etc/ssl`, with the corresponding keys at `/etc/ssl/private`."""
    cert_name = cert_name or os.path.basename(cert)
    key_name = key_name or os.path.basename(key)

    # small sanity check
    with open(cert) as f:
        if 'PRIVATE' in f.read():
            raise ValueError(
                'You seem to have passed a private key as a cert!')

    with open(key) as f:
        if 'PRIVATE' not in f.read():
            raise ValueError(
                '{} does not seem to be a valid private key'.format(key))

    # check if remote is reasonably secure
    cert_dir = config['sslcert_cert_dir']
    cert_dir_st = remote.lstat(cert_dir)

    if not cert_dir_st:
        raise ConfigurationError(
            'Remote SSL dir {} does not exist'.format(cert_dir))

    key_dir = config['sslcert_key_dir']
    key_dir_st = remote.lstat(key_dir)

    if not key_dir_st:
        raise ConfigurationError(
            'Remote key dir {} does not exist'.format(key_dir))

    SECURE_MODES = (0o700, 0o710)
    actual_mode = key_dir_st.st_mode & 0o777
    if actual_mode not in SECURE_MODES:
        raise ConfigurationError(
            'Mode of remote key dir {} is {:o}, should be one of {:o}'.format(
                key_dir, actual_mode, SECURE_MODES))

    if key_dir_st.st_uid != 0:
        raise ConfigurationError(
            'Remove key dir {} is not owned by root'.format(key_dir))

    # we can safely upload the key and cert
    cert_rpath = remote.path.join(cert_dir, cert_name)
    key_rpath = remote.path.join(key_dir, key_name)

    changed = False
    changed |= fs.upload_file(cert, cert_rpath).changed
    changed |= fs.upload_file(key, key_rpath).changed
    changed |= fs.chmod(key_rpath, 0o640).changed
    changed |= fs.chown(key_rpath, uid='root', gid='ssl-cert').changed

    if changed:
        return Changed(
            msg='Uploaded key pair {}/{}'.format(cert_name, key_name))

    return Unchanged(
        msg='Key pair {}/{} already uploaded'.format(cert_name, key_name))
Example #24
0
def upload_file(local_path,
                remote_path=None,
                follow_symlink=True,
                create_parent=False):
    """Uploads a local file to a remote and if does not exist or differs
    from the local version, uploads it.

    To avoid having to transfer the file one or more times if unchanged,
    different methods for verification are available. These can be configured
    using the ``fs_remote_file_verify`` configuration variable.

    :param local_path: Local file to upload. If it is a symbolic link, it will
                       be resolved first.
    :param remote_path: Remote name for the file. If ``None``, same as
                        ``local_path``. If it points to a directory, the file
                        will be uploaded to the directory. Symbolic links not
                        pointing to a directory are an error.

    :param return: ``False`` if no upload was necessary, ``True`` otherwise.
    """
    st, remote_path = _expand_remote_dest(local_path, remote_path)
    lst = os.stat(local_path) if follow_symlink else os.lstat(local_path)

    verifier = Verifier._by_short_name(config['fs_remote_file_verify'])()
    uploader = Uploader._by_short_name(config['fs_remote_file_upload'])()

    if lst is None:
        raise ConfigurationError(
            'Local file {!r} does not exist'.format(local_path))

    if S_ISLNK(lst.st_mode):
        # local file is a link
        rst = remote.lstat(remote_path)

        if rst:
            if not S_ISLNK(rst.st_mode):
                # remote file is not a link, unlink it
                remote.unlink(remote_path)
            elif remote.readlink(remote_path) != os.readlink(local_path):
                # non matching links
                remote.unlink(remote_path)
            else:
                # links pointing to the same target
                return Unchanged(
                    msg='Symbolink link up-to-date: {}'.format(remote_path))

        remote.symlink(os.readlink(local_path), remote_path)
        return Changed(msg='Created remote link: {}'.format(remote_path))

    if not st or not verifier.verify_file(st, local_path, remote_path):
        if create_parent:
            create_dir(remote.path.dirname(remote_path))

        uploader.upload_file(local_path, remote_path)

        if config.get_bool('fs_update_mtime'):
            times = (lst.st_mtime, lst.st_mtime)
            remote.utime(remote_path, times)
            log.debug('Updated atime/mtime: {}'.format(times))
        return Changed(msg='Upload {} -> {}'.format(local_path, remote_path))

    return Unchanged(msg='File up-to-date: {}'.format(remote_path))
Example #25
0
 def collect_fingerprints():
     fps = ''
     for key in key_names:
         if remote.lstat(key):
             fps += proc.run(['ssh-keygen', '-l', '-f', key])[0]
     return fps
Example #26
0
def upload_file(local_path,
                remote_path=None,
                follow_symlink=True,
                create_parent=False):
    """Uploads a local file to a remote and if does not exist or differs
    from the local version, uploads it.

    To avoid having to transfer the file one or more times if unchanged,
    different methods for verification are available. These can be configured
    using the ``fs_remote_file_verify`` configuration variable.

    :param local_path: Local file to upload. If it is a symbolic link, it will
                       be resolved first.
    :param remote_path: Remote name for the file. If ``None``, same as
                        ``local_path``. If it points to a directory, the file
                        will be uploaded to the directory. Symbolic links not
                        pointing to a directory are an error.

    :param return: ``False`` if no upload was necessary, ``True`` otherwise.
    """
    st, remote_path = _expand_remote_dest(local_path, remote_path)
    lst = os.stat(local_path) if follow_symlink else os.lstat(local_path)

    verifier = Verifier._by_short_name(config['fs_remote_file_verify'])()
    uploader = Uploader._by_short_name(config['fs_remote_file_upload'])()

    if lst is None:
        raise ConfigurationError('Local file {!r} does not exist'.format(
            local_path))

    if S_ISLNK(lst.st_mode):
        # local file is a link
        rst = remote.lstat(remote_path)

        if rst:
            if not S_ISLNK(rst.st_mode):
                # remote file is not a link, unlink it
                remote.unlink(remote_path)
            elif remote.readlink(remote_path) != os.readlink(local_path):
                # non matching links
                remote.unlink(remote_path)
            else:
                # links pointing to the same target
                return Unchanged(
                    msg='Symbolink link up-to-date: {}'.format(remote_path))

        remote.symlink(os.readlink(local_path), remote_path)
        return Changed(msg='Created remote link: {}'.format(remote_path))

    if not st or not verifier.verify_file(st, local_path, remote_path):
        if create_parent:
            create_dir(remote.path.dirname(remote_path))

        uploader.upload_file(local_path, remote_path)

        if config.get_bool('fs_update_mtime'):
            times = (lst.st_mtime, lst.st_mtime)
            remote.utime(remote_path, times)
            log.debug('Updated atime/mtime: {}'.format(times))
        return Changed(msg='Upload {} -> {}'.format(local_path, remote_path))

    return Unchanged(msg='File up-to-date: {}'.format(remote_path))