くりにっき

フルスタックキュアエンジニアです

bundler + rspecでfluentdプラグインを作るための手順

先日 fluent-plugin-out_chatwork を作ったのですが、fluentdの公式サイト だとtestunit使用を前提にしててrspec厨には厳しい世の中だったので自分用にrspecでfluentdプラグインを作るまでの手順をまとめてます。

コマンドを叩いた後のファイル出力は適宜コミットのURLをつけているので参照してください

0. 前提条件

rbenvrvm のようなrubyのバージョン管理ツールをインストールしてること。

このエントリではrbenvを前提に書きます。

1. ruby 1.9.3をインストールする

td-agent ver 1.1.20 時点でruby 1.9.3なので合わせておくのが無難。

rbenv install 1.9.3-p547

公式だと 1.9.3-p194 ってあるけどローカルで開発する分にはパッチバージョンまでは気にしなくていいと思う。

とは言えrubyの最新が2.1なので追随したいという欲求はあると思うので、並列ビルドするための手順は後述。

2. bundlerをインストールする

gem install bundler

ruby使っててbundler使ってないことはないはずだけど、新規でruby入れた時にはbundlerは入ってないので忘れがち

3. bundle gemでgemのスケルトンを作る

$ bundle gem fluent-plugin-out_chatwork
      create  fluent-plugin-out_chatwork/Gemfile
      create  fluent-plugin-out_chatwork/Rakefile
      create  fluent-plugin-out_chatwork/LICENSE.txt
      create  fluent-plugin-out_chatwork/README.md
      create  fluent-plugin-out_chatwork/.gitignore
      create  fluent-plugin-out_chatwork/fluent-plugin-out_chatwork.gemspec
      create  fluent-plugin-out_chatwork/lib/fluent/plugin/out_chatwork.rb
      create  fluent-plugin-out_chatwork/lib/fluent/plugin/out_chatwork/version.rb

https://github.com/sue445/fluent-plugin-out_chatwork/commit/f462b5a5d3d912fe860dad2a6f0b1c496d7bf5b4

fluentdのためのプラグインをイチから書く手順(bundler版) - tagomorisのメモ置き場 だと手動でディレクトリ構成変えてますが、今のbundlerだとハイフン区切りをディレクトリ階層の区切りにしてくれてる模様。*1

fluentdプラグイン命名規則

  • fluent-plugin-で始めて後ろに好きな名前をつける

bundle gem する時のハイフン区切りとアンスコ区切りの違いはこんな感じ

ハイフン区切り

ハイフンでディレクトリ作られるのでネームスペースの区切りとして使われる

$ bundle gem happiness-charge-precure
      create  happiness-charge-precure/Gemfile
      create  happiness-charge-precure/Rakefile
      create  happiness-charge-precure/LICENSE.txt
      create  happiness-charge-precure/README.md
      create  happiness-charge-precure/.gitignore
      create  happiness-charge-precure/happiness-charge-precure.gemspec
      create  happiness-charge-precure/lib/happiness/charge/precure.rb
      create  happiness-charge-precure/lib/happiness/charge/precure/version.rb

アンスコ区切り(単語区切り)

ディレクトリは作られないので単語区切り

$ bundle gem happiness_charge_precure
      create  happiness_charge_precure/Gemfile
      create  happiness_charge_precure/Rakefile
      create  happiness_charge_precure/LICENSE.txt
      create  happiness_charge_precure/README.md
      create  happiness_charge_precure/.gitignore
      create  happiness_charge_precure/happiness_charge_precure.gemspec
      create  happiness_charge_precure/lib/happiness_charge_precure.rb
      create  happiness_charge_precure/lib/happiness_charge_precure/version.rb

4. version.rbを消してgemspecにバージョンを直接書く

これは理由はよく分かってないのですが、fluentdのプラグインだとgemspecに直接バージョンを書いてることが多いのでfluentdの文化にあわせておくのが無難な気がする。(ファイルをtd-agentのpluginフォルダに置いて検証しやすくするため?)*3

5. 公式のサンプルコードをコピペする

作るプラグインの種類(インプットプラグインとかアウトプットプラグインとか)に応じてサンプルコードをコピペする

https://github.com/sue445/fluent-plugin-out_chatwork/commit/32f20ee6165c58a34ded4d374d1f166fa8a4617f

ファイル名が <TYPE>_<NAME>.rb になってないならこの時に合わせる

6. rspecのセットアップをする

gemspecに

spec.add_development_dependency "rspec", "~> 3.0.0"

を追加して

$ bundle install 

$ bundle exec rspec --init
  create   .rspec
  create   spec/spec_helper.rb

spec_helperにオプションがコメントアウトされていろいろ書いてあるので必要に応じて追加する。

config.order = 'random' があるとテストの実行順を毎回ランダムにしてくれるのでテストの順番に依存するテストがなくなるのでおすすめ

https://github.com/sue445/fluent-plugin-out_chatwork/commit/c711a9b424ed30c067ac9c6fc3a4499cfbb0af84

bundle gem 〜 -t すればspec_helper.rbと.rspecも作られるんですが、bundlerで作られるやつが書式が古いので後から rspec --init した方がいいと思うw

7. fluentdプラグインのためのrspecの設定を追加する

gemspec

  spec.add_dependency "fluentd"

spec_helper.rb

require 'fluent/load'
require 'fluent/test'

require 'fluent/plugin/out_chatwork' # ←自分のプラグインの名前に適宜変える

https://github.com/sue445/fluent-plugin-out_chatwork/commit/bb64ad36f4b205cbb13a8624ce8f362f96f6e8da

8. プラグインを作り始める

いきなりプラグインの処理を実装するよりも、設定が読めるかどうかのテストを一応書いた方がハードル低いと思う

サンプルspecはこんな感じ

describe Fluent::ChatworkOutput do
  let(:driver)   { Fluent::Test::OutputTestDriver.new(Fluent::ChatworkOutput, 'test.metrics').configure(config) }
  let(:instance) { driver.instance }

  describe "config" do
    let(:config) do
      %[
      api_token xxxxxxxxxxxxxxxxxxxx
      room_id   1234567890
      body      some message
      ]
    end

    it "should get api_token" do
      expect( instance.api_token ).to eq "xxxxxxxxxxxxxxxxxxxx"
    end

    it "should get room_id" do
      expect( instance.room_id ).to eq "1234567890"
    end

    it "should get body" do
      expect( instance.body ).to eq "some message"
    end
  end
end

let(:config) の中が

<match **>
  type         chatwork
  api_token    xxxxxxxxxxxxxxxxxxxx
  room_id      1234567890
  message      some message
</match>

のtype以降の行相当(typeだけはfluentd側で設定される)

https://github.com/sue445/fluent-plugin-out_chatwork/commit/6f8c82bf787c2896d12c93c92f49edceee5cceae

9. 思い思いにプラグインの処理を実装したりリファクタリングしたり

https://github.com/sue445/fluent-plugin-out_chatwork/commit/a109acb7849641942b3a5fdc77d758bd9c97e5c1

10. Travis CIで複数バージョンのrubyのテストを出来るようにする

Travis CIの設定はこちらを参照

GithubにあるリポジトリをTravis CI連携する手順 #junitbook - くりにっき

最小限の .travis.yml

language: ruby
rvm:
  - 1.9.3
  - 2.0.0
  - 2.1.2
script: bundle exec rspec

rvmの後にrubyのバージョン複数書いているのがポイント(こうするだけでtravisだと同時に複数バージョンテストできる)

https://travis-ci.org/sue445/fluent-plugin-out_chatwork

謝辞

id:bash0c7 さんの fluent-plugin-idobata を参考にさせてもらってます m( )m

*1:確か1.5時点ではこんな仕様だった気がする

*2:inとかoutとか

*3:古いbundlerだとversion.rbが作られなかったからその名残という説もある