2014/08/24

OpenRestyとLapisでSupervisorctlを実行できるシンプルなウェブアプリ作成

周りのマークアップエンジニアがgit使いこなしていて、平気でブランチを切り替えたりして開発サーバー上で作業して貰っています。ただし、ブランチ切り変えになると、たまにアプリリスタートも必要で、マークアップじゃ一人で作業したいブランチを開発サーバーに反映できない場合があります。

Plack::Loader::Shotgunを使ってアプリさえ実行すれば、こういった問題をよりやすく解決できる気もしますが、一応、Nginx OpenRestyを使ってみたかったので、試しにウェブインタフェースを使ってプロセスをリスタートできるアプリを作っちゃいました。

*OpenRestyについてはOpenRestyの公式ページを参考にしてください

**フレームワークはLapisというOpenResty上で動くLuaのウェブフレームワークです。おしゃんてぃでおすすめです。

最近process管理のため主にSupervisordを使っています。周りの人がそれをrootを使って実行しガチなんですが、やっぱり開発環境といってもウェブのユーザがrootのプロセスが実行できたら怖いですし、nginx自体も普段nobodyユーザによって実行されるので、まずSupervisordをnobodyユーザとして実行しました。 そこでnobodyがアクセスできるディレクトリを作って、以下のlapis app.luaを作成しました:
--app.lua
local lapis = require("lapis")
local app_helpers = require("lapis.application")
local validate = require("lapis.validate")
local cjson = require("cjson")

local capture_errors = app_helpers.capture_errors

local app = lapis.Application()
app:enable("etlua")
app.layout = false

validate.validate_functions.alphanumeric = function(input)
     return string.match(input, "^[%w_%-]+$"), "must be alphanumeric"
end

-- たたいたコマンドのSTDOUTパージング
function app:parse_status(line)
    local parts = {}
    for word in line:gmatch("%S+") do table.insert(parts, word) end

    local status = {
        ["name"] = parts[1],
        ["status"] = parts[2],
    }
    if status["status"] == 'RUNNING' then
        status["pid"] = string.gsub(parts[4], ",", "")
        status["uptime"] = parts[#parts]
    elseif status["status"] == 'STOPPED' then
        status["uptime"] = string.format("%s %s %s", parts[3], parts[4], parts[5])
    end

    return status
end

-- トップページにstatusの結果をテーブルで表示したいので、結果をselfにいれるとテンプレートで使えるようになる
app:get("/", function(self)
    local handle = io.popen("/usr/bin/supervisorctl status" .. " 2>&1")

    self.supervisor_status = {}
    for line in handle:lines() do
        table.insert(
            self.supervisor_status,
            self.app:parse_status(line)
        )
    end

    handle:close()

    return { render = 'index' }
end)

-- 最低限のvalidationとリスタートをかける処理
app:post("/:app_name/restart/", capture_errors(function(self)
    validate.assert_valid(self.params, {
        { "app_name", exists = true, alphanumeric = true }
    })

    local app_name = self.params.app_name
    local handle = io.popen("/usr/bin/supervisorctl restart " .. app_name .. " 2>&1")

    self.message = {}
    for line in handle:lines() do
        table.insert(self.message, line)
    end

    return cjson.encode(self.message)
end))

return app
.

出来上がったものはこんな感じ: