3 minute read

다음 사이드 프로젝트slack 봇(bot)이다. 관리 시간을 줄일 아이디어가 떠올랐다. 언어는 요즘 쓰고 있는 elixir 언어로 결정했다. 처음부터 만들어야 하는 거 아니야? 노노. 이쪽 생태계를 무시하면 안 된다. 1986년에 발표된 32살 erlang 언어가 뒤를 든든하게 받쳐주고 있다. 엔간한 건 다 있다. elixir에 없으면 erlang 라이브러리를 찾으면 된다.

ping 슬랙 봇

elixir 설치부터 한다.

$ mix new ping --sup

프로젝트 생성

# mix.exs
defp deps do
  [
    {:slack, "~> 0.13.0"}
  ]
end

elixir로 만든 slack RTM(Real Time Messaging) API 클라이언트 라이브러리가 있다. 디펜던시(dependency)에 slack 라이브러리를 추가한다.

defmodule Ping.Slack do
  use Slack

  def start_link(args) do
    Slack.Bot.start_link(Ping.Slack, args, Application.get_env(:ping, :slack_token))
  end

  def child_spec(opts) do
    %{
      id: __MODULE__,
      start: {__MODULE__, :start_link, [opts]},
      type: :worker,
      restart: :permanent,
      shutdown: 500
    }
  end

  def handle_event(message = %{type: "message"}, slack, state) do
    if message.text == "ping" do
      send_message("pong", message.channel, slack)
    end

    {:ok, state}
  end

  def handle_event(_, _, state), do: {:ok, state}
  end

lib/ping/slack.ex 파일을 생성한다. handle_event 콜백 함수를 구현하면 된다. 봇이 있는 채널 메시지나 DM(direct message)을 어떻게 처리할지 결정한다. 여기선 ping 메시지를 검사해 pong 메시지를 채널에 쓰게 했다. 크래시가 나면 종료 조건 없이 재시작하게 child_spec 함수를 구현했다. 구현 위치는 supervisor tree를 어떻게 구성하느냐에 따라 다르다. 여기선 별 생각 없이 구성했다.

defmodule Ping.Application do
  @moduledoc false

  use Application

  def start(_type, _args) do
    children = [{Ping.Slack, []}]

    opts = [strategy: :one_for_one, name: Ping.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

lib/ping/application.ex 파일을 수정한다. 위에서 만든 Ping.Slack 모듈을 자식으로 가진 supervisor를 시작한다. supervisor는 재시작을 담당한다.

use Mix.Config

config :ping,
  slack_token: ""

config/config.exs 파일에 추가한다. 이제 slack_token 발급받아야 한다.

slack apps 페이지에서 앱을 만든 후 OAuth & Permissions > Bot User OAuth Access Token 문자열을 복사한다. 다시 config.exs 파일로 돌아와 slack_token에 붙여넣는다.

slack app 허가 권한은 workspace owner에게 있다. 적용하고 싶은 workspace의 owner가 아니라면 테스트용 workspace를 만드는 걸 추천.

$ mix run --no-halt

mix.exs 파일이 있는 ping 프로젝트 루트 디렉터리에서 실행하면 ping 봇이 짠하고 나타난다.

heroku로 배포

gigalixir는 elixir 언어 웹프레임워크인 phoenix만 지원한다. 그래서 heroku로 왔다. @book_quote_bot, @bquote_bot 트위터봇 세팅하고 처내버려 두다가 정말 오랜만에 들어왔다.

heroku에서 앱을 만들고 안내대로 쭉쭉 따라가면 된다.

$ heroku buildpacks:add "https://github.com/HashNuke/heroku-buildpack-elixir.git"
Buildpack added. Next release on ping-slack-bot will use https://github.com/HashNuke/heroku-buildpack-elixir.git.
Run git push heroku master to create a new release using this buildpack.

공식 지원 언어가 아니라 빌드팩(buildpack)을 설정해줘야 한다. 다행히 elixir 빌드팩이 있다.

$ git push heroku master
...
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Elixir app detected
remote: -----> Checking Erlang and Elixir versions
remote:        WARNING: elixir_buildpack.config wasn't found in the app
remote:        Using default config from Elixir buildpack
remote:        Will use the following versions:
remote:        * Stack heroku-16
remote:        * Erlang 20.1
remote:        * Elixir 1.5.0
remote: -----> Will export the following config vars:
remote:        * MIX_ENV=prod
remote: -----> Using cached Erlang 20.1
remote: -----> Installing Erlang 20.1
remote:
remote: -----> Using cached Elixir v1.5.0
remote: -----> Installing Elixir v1.5.0
...
remote: -----> Compiling
remote: Compiling 3 files (.ex)
remote: Generated pong app
remote: -----> Creating .profile.d with env vars
remote: -----> Writing export for multi-buildpack support
remote: -----> Discovering process types
remote:        Procfile declares types     -> (none)
remote:        Default types for buildpack -> web
remote:
remote: -----> Compressing...
remote:        Done: 61.9M
remote: -----> Launching...
remote:        Released v5
remote:        https://pong-slack-bot.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/pong-slack-bot.git
   6bce8f3..b0f3b69  master -> master

push만 하면 컴파일부터 배포까지 알아서 싹 해준다. 이 맛에 PaaS(Platform as a Service) 쓰는 거제~

참고