Posted at

lsyncdでローカルファイルの更新をS3 バケットに同期する

More than 3 years have passed since last update.

2016/5/13 JAWS-UG アーキテクチャ専門支部 ハイブリッドクラウド分科会 CDP議論会 #5 の、発表枠番外編で紹介したもの。

lsyncd は、デフォルトでは rsync を使ってファイルのコピーを行いますが、じつは中で Lua の処理理系が動いていて、設定ファイルは Lua のスクリプトになっています。ということは、inotify で通知を受け取ったら実行したい処理を Lua で書けば、好きなことができます。いくつかサンプルも用意されています。

というわけで、書いてみたのがこんなファイル。

AWS CLI をつかって aws s3 cp を実行します。3回トライしてダメだったら aws sns publish で通知します。

Lua から外部コマンドとして、aws s3 cp を呼んで終了コードを参照してリトライ…なんてことができればよいのですが、単純に aws s3 cp を呼ぶと、終了コードが非0で返って来ると lsyncd ごと終了してしまうので、シェルのワンライナーで処理しています。

Lua の文と、文字列として埋め込まれたシェルのワンライナーが混じってわかりにくいですが、local runcmd = がワンライナーを組み立てている部分です。

ワンライナーの最後の || : は終了コードを上書きするためのコメント文です。

source_dir = "/home/lsync/upload"

s3bucket = "my-s3-bucket"
prefix = "my-prefix"

sns_topic_arn = "SNS_TOPIC_ARN"
msg_subject = "SNS_MSG_SUBJECT"
snscmd = "aws sns publish --topic-arn '" .. sns_topic_arn .. "' --subject '" .. msg_subject .. "'"

settings {
logfile = "/home/lsync/lsyncd.log",
statusFile = "/home/lsync/lsyncd.status",
nodaemon = false,
statusInterval = 1,
delay = 5,
}

cp = function(event)
local src_path = event.sourcePathname
local dst_path = event.targetPathname

if (string.sub(event.source, -1, -1) == "/") then
src_path = string.sub(event.source, 1, -2) .. event.pathname
end
if (string.sub(event.target, -1, -1) == "/") then
dst_path = string.sub(event.target, 1, -2) .. event.pathname
end
local s3cmd = "aws s3 cp '" .. src_path .. "' '" .. dst_path .. "'"
local msg_body = "command failed: " .. s3cmd
local msg = " --message '" .. string.gsub(msg_body, "'", "\"") .. "'"

local runcmd = "rc=0 && [ -f '" .. src_path .. "' ] && for try in 1 2 3; do " .. s3cmd .. "; rc=$?; [ $rc -eq 0 ] && break; done || " .. snscmd .. msg .. " || :"
spawnShell(event, runcmd)
end

rm = function(event)
local src_path = event.sourcePathname
local dst_path = event.targetPathname

if (string.sub(event.source, -1, -1) == "/") then
src_path = string.sub(event.source, 1, -2) .. event.pathname
end
if (string.sub(event.target, -1, -1) == "/") then
dst_path = string.sub(event.target, 1, -2) .. event.pathname
end

local s3cmd = "aws s3 rm '" .. dst_path .. "'"
local msg_body = "command failed: " .. s3cmd
local msg = " --message '" .. string.gsub(msg_body, "'", "\"") .. "'"
local runcmd = "rc=0 && [ ! -f '" .. src_path .. "' ] && for try in 1 2 3; do " .. s3cmd .. "; rc=$?; [ $rc -eq 0 ] && break; done || " .. snscmd .. msg .. " || :"
spawnShell(event, runcmd)
end

mv = function(event)
local src_path = event.o.targetPathname
local dst_path = event.d.targetPathname

if (string.sub(event.o.target, -1, -1) == "/") then
src_path = string.sub(event.o.target, 1, -2) .. event.o.pathname
end
if (string.sub(event.d.target, -1, -1) == "/") then
dst_path = string.sub(event.d.target, 1, -2) .. event.d.pathname
end
local s3cmd = "aws s3 mv '" .. src_path .. "' '" .. dst_path .. "'"
local msg_body = "command failed: " .. s3cmd
local msg = " --message '" .. string.gsub(msg_body, "'", "\"") .. "'"

local runcmd = "rc=0 && [ -f '" .. src_path .. "' ] && for try in 1 2 3; do " .. s3cmd .. "; rc=$?; [ $rc -eq 0 ] && break; done || " .. snscmd .. msg .." || :"
spawnShell(event, runcmd)
end

s3sync = {
maxProcesses = 1,
onCreate = cp,
onModify = cp,
onDelete = rm,
-- onMove = mv,
}

sync {
s3sync,
source = source_dir,
target = "s3://" .. s3bucket .. "/" .. prefix,
}