watchdog と paramiko を使って、ローカルで編集したファイルを瞬時にサーバーにアップロードする

ローカルのファイルシステムの監視。WindowsではGetDirectoryChangesW(だったっけ?)とかいうAPIがあって、Linuxにはinotifyがあるし、Macにもなにか手段があるはずなんだけど、 http://packages.python.org/watchdog/installation.html watchdog というライブラリを使えばAPIの違いを気にしないで監視できる。 watchdog すげー。

ファイルのアップロードとリモートでのコマンド実行。毎回scpコマンドを叩くのでも良いけど、 http://www.lag.net/paramiko/ paramiko というライブラリを使えば、常に ssh 接続を1本用意しておいてその上でファイルのアップロードもコマンド実行もできる。 paramiko すげー。

#!/usr/bin/env python

import os
import sys
import time

import paramiko
from watchdog import events, observers


def make_sftp(remote):
    if '@' in remote:
        user, host = remote.split('@')
    else:
        user = None
        host = remote

    try:
        config_file = open(os.path.expanduser('~/.ssh/config'))
        config = paramiko.config.SSHConfig()
        config.parse(config_file)
        conf = config.lookup(host)
        if user is None:
            user = conf.get('user')
        if hostname in conf:
            host = hostname
    except Exception:
        pass

    ssh = paramiko.SSHClient()
    #ssh.load_host_keys()
    ssh.load_system_host_keys()
    ssh.connect(host, username=user)
    sftp = ssh.open_sftp()
    sftp.ssh = ssh
    return sftp


REMOTE_HOST = None
LOCAL_BASE = REMOTE_BASE = None
SFTP = None

def put_file(local_path):
    global SFTP
    if SFTP is None:
        SFTP = make_sftp(REMOTE_HOST)
    relpath = os.path.relpath(local_path, LOCAL_BASE)
    if relpath.startswith('..'):
        print "%s is out of base."
        return
    remote_path = os.path.join(REMOTE_BASE, relpath)
    print "%s => %s" % (local_path, remote_path)
    SFTP.put(local_path, remote_path)


class SyncEventHandler(events.FileSystemEventHandler):
    def _sync(self, event):
        put_file(event.src_path)

    def on_created(self, event):
        self._sync(event)
        super(SyncEventHandler, self).on_created(event)

    def on_modified(self, event):
        self._sync(event)
        super(SyncEventHandler, self).on_created(event)

def main(src_dir, host, remote_dir):
    global REMOTE_HOST, LOCAL_BASE, REMOTE_BASE, SFTP
    REMOTE_HOST = host
    REMOTE_BASE = remote_dir
    LOCAL_BASE = src_dir
    SFTP = make_sftp(REMOTE_HOST)

    handler = SyncEventHandler()
    observer = observers.Observer()
    observer.schedule(handler, LOCAL_BASE, True)
    observer.start()
    try:
        while True:
            observer.join(1)
    except KeyboardInterrupt:
        observer.stop()
    try:
        while observer.is_alive():
            observer.join(1)
    except:
        pass

USAGE = """\
sync_local_changes.py LOCAL_BASE [user@]HOST REMOTE_BASE
"""

if __name__ == '__main__':
    if len(sys.argv) == 4:
        main(*sys.argv[1:])
    else:
        print USAGE
このブログに乗せているコードは引用を除き CC0 1.0 で提供します。