LoginSignup
9
6

More than 5 years have passed since last update.

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

Posted at

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,
}
9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6