はじめに
定時にSlackのチャンネルに始業・終業のチャイムを投げるNervesをこしらえました。
@torifukukaiou さんの『NHK番組表API叩いてSlackで通知する[With Elixir]』をとてもとても参考にさせていただきました。
前提
-
mix nerves.new slackchime --nerves-pack
コマンドでプロジェクトを作成 - ネットワークの設定は完了している
- SlackのIncoming WebhookのURLを取得(生成)している
こんなのこしらえたよ
動きはきっとこう
Nerves起動時にlib/slackchime/application.ex
が呼び出されてSupervisor Treeにモジュールを登録 → {Slackchime.Scheduler, []}
↓
Slackchime.Scheduler
(lib/slackchime/scheduler.exで定義)でQuantumで利用するotp_appを読み込み → use Quantum, otp_app: :slackchime
↓
config/config.exs
に設定しているjobsに書かれたモジュールを定期的に実行 → config :slackchime, Slackchime.Scheduler, jobs: [...]
対象ファイル
slackchime/
config/
config.exs
lib/
slackchime/
application.ex
scheduler.ex
slackchime.ex
mix.exs
mix.exs
depsに以下を追加して、mix deps.get
します。
defp deps do
{:httpoison, "~> 1.6"},
{:jason, "~> 1.2"},
{:quantum, "~> 3.0-rc"},
{:timex, "~> 3.5"}
end
lib/slackchime.ex
実際のプログラムはこのような感じにしています。
config/config.exs
に設定した値をとってきてみたかったのでApplication.get_env()
を使ってます
Slackchime.ring()
を呼ぶとSlackで設定したチャンネル or 個人などにメッセージを投げます。
defmodule Slackchime do
@slack_incoming_hook_urls Application.get_env(:slackchime, :slack_incoming_hook_urls)
@chime_01 Application.get_env(:slackchime, :chime_01)
def ring(greeting) do
message =
case greeting do
"start_of_work" -> "おはようございます!"
"start_of_lunch" -> "お昼休みです!ご飯をもりもり食べましょう(^¬^)"
"end_of_lunch" -> "お昼休み終わりました!"
"end_of_work" -> "今日も一日おつかれさまでした!"
nil -> "..."
_ -> greeting
end
slack_post(message)
end
defp slack_post(message) do
body =
slack_post_body(message)
|> Jason.encode!()
header = slack_post_header()
for slack_incoming_hook_url <- @slack_incoming_hook_urls do
HTTPoison.post!(slack_incoming_hook_url, body, header)
end
end
defp slack_post_body(message) do
%{
text: @chime_01 <> "\n" <> message
}
end
defp slack_post_header() do
[{"Content-type", "application/json"}]
end
end
lib/slackchime/scheduler.ex
cron的な動きをするQuantumの利用を宣言します。
これはSupervisor Treeから呼ばれるだけのモジュールで、実際のcrontab的な設定はconfig/config.ex
に記載します。
今回は:slackchime
という名前で宣言しました。
defmodule Slackchime.Scheduler do
use Quantum, otp_app: :slackchime
end
lib/slackchime/application.ex
Supervisor TreeにQuantumを登録します。
def start(_type, _args) do
opts = [strategy: :one_for_one, name: Slackchime.Supervisor]
children =
[
{Slackchime.Scheduler, []}
] ++ children(target())
Supervisor.start_link(children, opts)
end
config/config.ex
変数の設定とQuantumのジョブの設定をします。
- 営業日は月曜日から金曜日
-
timezone "Asia/Tokyo"
の設定をするとエラーだったのでUTCで記載 - 営業日の日本時間9時に
Slackchime.ring("start_of_work")
を実行 - 営業日の日本時間12時に
Slackchime.ring("start_of_lunch")
を実行 - 営業日の日本時間13時に
Slackchime.ring("end_of_lunch")
を実行 - 営業日の日本時間18時に
Slackchime.ring("end_of_work")
を実行
#
# 変数設定
#
config :slackchime,
slack_incoming_hook_urls:
["https://hooks.slack.com/services/*********/***********/************************"], # リストで書くと複数にポスト
chime_01: "キーンコーンカーンコーン♪"
#
# cron的に使うjobs設定
# UTCで記載する(timezoneの設定をするとエラー)
#
config :slackchime, Slackchime.Scheduler,
jobs: [
# {"* * * * *", {Slackchime, :ring, ["start_of_lunch"]}}, # テスト用 毎分実行
# {"* * * * Mon-Fri *", {Slackchime, :ring, ["てすとー"]}}, # テスト用 毎分実行 月曜から金曜の間
{"0 0 * * Mon-Fri *", {Slackchime, :ring, ["start_of_work"]}},
{"0 3 * * Mon-Fri *", {Slackchime, :ring, ["start_of_lunch"]}},
{"0 4 * * Mon-Fri *", {Slackchime, :ring, ["end_of_lunch"]}},
{"0 9 * * Mon-Fri *", {Slackchime, :ring, ["end_of_work"]}}
]
Nervesにアップロード
upload.sh
を使ってイメージをアップロードします。
※192.168.5.55はNervesのIPアドレスです。
~> mix firmware
~> ./upload.sh 192.168.5.55
結果
Nervesが起動すると定時にSlackにメッセージが投げられていることが確認できました\(^o^)/
うまくいっていないこと
解決しました!!
Nervesのパーミッションの問題だと思うんですが"/srv/erlang/lib/tzdata-1.0.3/priv/latest_remote_poll.txt"
に書き込みができず約3秒に1回エラーが出てしまってます。シェルに落ちてパーミッション追加しようとしてもchmod
コマンドがなくお手上げでした(´・ω・`)
エラーの例。
07:44:10.633 [error] GenServer :tzdata_release_updater terminating
** (File.Error) could not write to file "/srv/erlang/lib/tzdata-1.0.3/priv/latest_remote_poll.txt": read-only file system
(elixir 1.10.2) lib/file.ex:1050: File.write!/3
(tzdata 1.0.3) lib/tzdata/data_loader.ex:44: Tzdata.DataLoader.last_modified_of_latest_available/1
(tzdata 1.0.3) lib/tzdata/release_updater.ex:81: Tzdata.ReleaseUpdater.loaded_tzdata_matches_remote_last_modified?/0
(tzdata 1.0.3) lib/tzdata/release_updater.ex:42: Tzdata.ReleaseUpdater.poll_for_update/0
(tzdata 1.0.3) lib/tzdata/release_updater.ex:19: Tzdata.ReleaseUpdater.handle_info/2
(stdlib 3.12.1) gen_server.erl:637: :gen_server.try_dispatch/4
(stdlib 3.12.1) gen_server.erl:711: :gen_server.handle_msg/6
(stdlib 3.12.1) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: :check_if_time_to_update
State: []
まとめ
Quantumを使ってNervesでcron的な定期的にモジュールを実行することができました。
@torifukukaiou さんの記事にとても助けていただきました!!ありがとうございました!