空白空行を無視するrspec-match_fuzzyを作った

なんだかんだで必要になるので、空白空行を無視するrspecのmatcher、rspec-match_fuzzyを作った。

github.com

以下の例は空白空行を無視してマッチする。

it '...' do
  str1 = <<-EOS
    London Bridge
    Is Broken down,
    Dance over my Lady Lee.
    London Bridge
    Is Broken down
    With a gay Lady.
  EOS

  str2 = <<-EOS
    London  Bridge
    Is  Broken  down,

    Dance over my Lady Lee.


    London\tBridge
    Is\tBroken\tdown
    With a gay Lady.
  EOS

  expect(str1).to match_fuzzy str2
end

差異がある場合はdiffが出る。

f:id:winebarrel:20160409220635p:plain

また、ERBのヘルパーメソッドも追加した。

include RSpec::MatchFuzzy

it '...' do
  result = erb("<%= @foo %>, <%= @bar %>", foo: 100, bar: 'zoo')
  expect(result).to eq '100, zoo'
end

Ridgepole v0.6.4

業務でも使ってみて問題なさそうな感じなので v0.6.4 をリリースしました。

github.com

主な変更点は以下の通りです。

  • Add --use-external-script option
  • Add --mysql-use-alter option
  • Add --alter-extra option
  • Add --dump-with-default-fk-name option
  • Support t.index
  • Remove migration_comments support
  • Fix fk apply order

--use-external-script / --mysql-use-alter

以前の記事にも書きましたが、--use-external-scriptは外部スクリプトで変更を実施するためのオプションです。

https://github.com/winebarrel/ridgepole#execute-sql-using-external-script

$ cat test.sh
#!/bin/sh
SQL="$1"
CONFIG_JSON="$2"
echo "$SQL" | mysql -u root my_db

$ ridgepole -c config.yml --apply --external-script ./test.sh

変更を適用するときに、SQLを外部のスクリプトに渡すことができるようになります。 --mysql-use-alterはそれに関連して、CREATE/DROP INDEXの代わりにALTER TABLEを使うようになるオプションです。 この方が外部スクリプトでのパースが楽なので…

業務では、CIで「実行時間長すぎ」と言われたクエリについては、自動的に「pt-oscでクエリを実行する」スクリプトを出力して、それを手動実行みたいなことをやっています。がんばればpt-oscで勝手に適用、とかまでできると思いますが、そこまでやったことはないです。

--alter-extra

--alter-extraMySQLのALRER文にLOCK=NONEとかつけるためのオプションです。

https://github.com/winebarrel/ridgepole#add-extra-statement-to-alter

$ ridgepole -a -c database.yml --alter-extra="LOCK=NONE" --debug
Apply `Schemafile`
...
-- add_column("dept_manager", "to_date2", :date, {:null=>false, :after=>"from_date"})
   (42.2ms)  ALTER TABLE `dept_manager` ADD `to_date2` date NOT NULL AFTER `from_date`,LOCK=NONE
   -> 0.0428s
-- remove_column("dept_manager", "to_date")
   (46.9ms)  ALTER TABLE `dept_manager` DROP `to_date`,LOCK=NONE
   -> 0.0471s

--dump-with-default-fk-name / fk apply order

外部キーに関して二点ほど。

Railsでadd_foreign_keyを実行したとき、名前をつけないと自動的にfk_rails_e74ce85cbcみたいな名前がつけられるんですよね。 ただ、この名前だと、スキーマをダンプしたときにname: が出力されない…

ridgepole的には外部キーに名前ついていないと困るので、--dump-with-default-fk-nameオプションで強制的にダンプできるようにしました。

あと、外部キーに関してはテーブルの作成順を考慮しないとエラーになる問題があったのですが、どういう定義順であっても、まずテーブルの作成を行ってから、外部キーを張るように修正しました。

Support t.index / Remove alias_method_chain

プルリクをいただきまして、

t.indexがサポートされて、alias_method_chainが削除されました。 Rails5対応への足がかりができて、ありがたいことです。

また、それに付随して、migration_commentsのサポートを切りました。 migration_commentsについては対応状況が微妙だったのと、prependとalias_method_chainが同居する状況は避けたかったので、外した感じです。 今後も追加はないかなぁ…

次バージョンついて

とりあえず、以下を進めていきたいなぁ…と考えています。

  • Rails5サポート
  • MySQL 5.7サポート
    • 特にJSONカラムと生成カラム
  • 可能ならViewのサポート
    • MySQLでうまい手が見つからないのですが…

miam 0.2.3.beta: カスタムManagedPolicyサポート

某カンファレンスの裏側で、ひっそりとIAM管理ツールmiamv0.2.3.betaをリリースしました。

github.com

「対応しないと」と思いつつ対応できていなかったカスタムManagedPolicyをサポートしました。

managed_policy "my-policy", :path=>"/" do
  {"Version"=>"2012-10-17",
   "Statement"=>
    [{"Effect"=>"Allow", "Action"=>"directconnect:Describe*", "Resource"=>"*"}]}
end

user "bob", :path => "/developer/" do
  login_profile :password_reset_required=>true

  groups(
    "Admin"
  )

  policy "bob-policy" do
    # ...
  end

  attached_managed_policies(
    "arn:aws:iam::123456789012:policy/my-policy"
  )
end

AWS側バージョン管理との整合性について

「gitで管理してください」というスタンスです。

基本的にAWS側のバージョンはデフォルトを見て更新を行います。 更新する場合には、新しいpolicy versionを作ってそれをデフォルトにします。 また、バージョン数が5を超える場合は古くてデフォルトになっていないバージョンを削除してから新しいpolicy versionを作成します。

gitの管理とAWS側のバージョン管理は相性悪いので、miamで「バージョンを指定する」などの機能をサポートするつもりは、今のところないです。

Hexにライブラリを登録してみた

Elixirの勉強の一環としてHexにライブラリを登録してみた。

banner | Hex

github.com

Ubuntusysvbannerをポートした小さいライブラリ。

iex(2)> IO.puts Banner.banner("hello")

 #    #  ######  #       #        ####
 #    #  #       #       #       #    #
 ######  #####   #       #       #    #
 #    #  #       #       #       #    #
 #    #  #       #       #       #    #
 #    #  ######  ######  ######   ####
:ok

ここ2〜3ヶ月、Elixirを触ってみた所感。

  • 悪くない…と思う。Goよりは性に合っている
  • 英語ドキュメントが大変だけど、かなり充実していると思う
  • たまにRubyの便利メソッドがほしくなる…程度の標準ライブラリの充実具合
  • 破壊的変更をしないスタイルにはまだなれない部分がある。さしあたってStringIOの代替を捜索中
  • OTPの世界観にもなかなかなれない。理念はわかるけど、MySQLのドライバがGenServerだったりするのを見ると「うーん、これはいい設計なのだろうか…」と思ったりする
  • エラーの投げ方、受け取り方についてのスタンダードがまだ疑問。「無視してリスタート」がやり方らしいが、すべてにそれが通用するか不明。あと、例外はあんまり使わない、とチュートリアルに書いてある割に、よく見かけるような
  • mixやhex—Rubyでいうところのbundler・gemあたりが整っているのはすばらしい。やっぱセントラルリポジトリからライブラリ落としたり、登録したりするのは便利だし楽しい

ちまたで騒がれているような熱狂的な感動はまだないけど、素性は良さそうなのでしばらく続けてみる。

メールアドレスをマスクしてpostfixのログをfluentdに流す

前回の記事fluent-plugin-filter-parse-postfixはデフォルトでメールアドレスをマスクするようにしている。

<source>
  @type tail
  path /var/log/maillog
  pos_file /var/log/td-agent/postfix-maillog.pos
  tag postfix.maillog
  format none
</source>

<filter postfix.maillog>
  @type grep
  regexp1 message status=
</filter>

<filter postfix.maillog>
  @type parse_postfix
  mask true # デフォルト値
  #include_hash ture
</filter>
{
  "time":"Feb 27 09:02:38",
  "hostname":"MyHOSTNAME",
  "process":"postfix/smtp[26490]",
  "queue_id":"5E31727A35D",
  "to":"*********@myemail.net",
  "domain":"myemail.net",
  "relay":"gateway-f1.isp.att.net[204.127.217.17]:25",
  "conn_use":2, // 一応数値型…
  "delay":0.58, // 一応数値型…
  "delays":"0.11/0.03/0.23/0.20",
  "dsn":"2.0.0",
  "status":"sent",
  "status_detail":"(250 ok ; id=en4req0070M63004172202102)"
}

メールアドレスはstatus_detailなどにも入っているが、それらについてもマスクするようにしている。(spec) あと、 include_hash tureというオプションを追加するとsha512のハッシュ値も付加されて、マスクされたデータを検索することもできる*1

なんでまあ、一応便利に使ってます。

*1:要openssl-devel/libssl-dev

postfixのログをfluentdに入れる a.k.a. ぅゎrubyっょぃ

Postfixのログの「status=...」の行をfluentdに流そうと思ったんですよ。

要件としては

  • メアドはマスクしたい
  • メアドからドメインとりたい
  • statusは値と詳細に分けたい
  • 大量にメールが流れるので早くしたい

それで何も考えずにCの拡張ライブラリを作って、それでflunetdのプラグインを書いたんですよ。

github.com

github.com

以下のような感じで、fluentdにログを流せます。

<source>
  @type tail
  path /var/log/mail.log
  pos_file /var/log/td-agent/postfix-mail.log.pos
  tag postfix.mail
  format none
</source>

<filter postfix.mail>
  @type grep
  regexp1 message status=
</filter>

<filter postfix.mail>
  @type parse_postfix
</filter>

<match postfix.mail>
  @type stdout
</match>

ただ、そもそも既存の処理のベンチマークを取っていなかったので、EC2/t2.microで速度を測ったんですよ。

gist.github.com

ちなみに、正規表現は以下の記事を参考にしました。

SIOS ビッグデータ技術ブログ: Postfixのログをfluentdを使ってTreasureDataに送る

で、結果が次の通り。

                           user     system      total        real
07:09:55 psl(1):       2.450000   0.000000   2.450000 (  2.450314)
07:09:58 psl(2):       2.460000   0.000000   2.460000 (  2.464074)
07:10:00 psl(3):       2.830000   0.000000   2.830000 (  2.834007)
07:10:03 psl(4):       2.500000   0.000000   2.500000 (  2.505221)
07:10:05 psl(5):       2.570000   0.000000   2.570000 (  2.565137)
>sec/prs:              0.000005   0.000000   0.000005 (  0.000005)
>prs/sec:            195160.031226        Inf        Inf (195026.773633)
                           user     system      total        real
07:10:11 psl+m(1):     2.620000   0.000000   2.620000 (  2.623226)
07:10:14 psl+m(2):     2.610000   0.000000   2.610000 (  2.600343)
07:10:16 psl+m(3):     2.590000   0.000000   2.590000 (  2.594375)
07:10:19 psl+m(4):     2.560000   0.000000   2.560000 (  2.557504)
07:10:21 psl+m(5):     2.560000   0.000000   2.560000 (  2.563402)
>sec/prs:              0.000005   0.000000   0.000005 (  0.000005)
>prs/sec:            193199.381762        Inf        Inf (193216.547290)
                           user     system      total        real
07:10:27 rgx(1):       6.420000   0.000000   6.420000 (  6.423937)
07:10:33 rgx(2):       6.410000   0.000000   6.410000 (  6.402610)
07:10:40 rgx(3):       6.410000   0.000000   6.410000 (  6.415075)
07:10:46 rgx(4):       6.470000   0.000000   6.470000 (  6.465956)
07:10:53 rgx(5):       6.560000   0.000000   6.560000 (  6.564861)
>sec/prs:              0.000013   0.000000   0.000013 (  0.000013)
>prs/sec:            77471.335606        Inf        Inf (77465.479252)
                           user     system      total        real
07:11:02 rgx+m(1):     7.110000   0.000000   7.110000 (  7.110184)
07:11:09 rgx+m(2):     6.940000   0.000000   6.940000 (  6.938133)
07:11:16 rgx+m(3):     6.970000   0.000000   6.970000 (  6.968497)
07:11:23 rgx+m(4):     7.150000   0.000000   7.150000 (  7.157125)
07:11:30 rgx+m(5):     6.950000   0.000000   6.950000 (  6.945317)
>sec/prs:              0.000014   0.000000   0.000014 (  0.000014)
>prs/sec:            71184.510251        Inf        Inf (71186.016988)

秒間19万行をパースする速度が必要だったかといえばそんなわけはなく。

Rubyは十分速いなぁ…先に速度測れよなぁ…という教訓が得られました。

ridgepole+alter .. , lock=none

Ridgepole v0.6.4で、ALTER文にLOCK=NONEとか付けられるようにしました。

$ ridgepole -a -c '{adapter: mysql2, database: employees}' --alter-extra="LOCK=NONE" --dry-run
Apply `Schemafile` (dry-run)
add_column("dept_manager", "to_date2", :date, {:null=>false, :after=>"from_date"})
remove_column("dept_manager", "to_date")

# ALTER TABLE `dept_manager` ADD `to_date2` date NOT NULL AFTER `from_date`,
# LOCK=NONE
# ALTER TABLE `dept_manager` DROP `to_date`,
# LOCK=NONE

$ ridgepole -a -c '{adapter: mysql2, database: employees}' --alter-extra="LOCK=NONE" --debug
Apply `Schemafile`
...
-- add_column("dept_manager", "to_date2", :date, {:null=>false, :after=>"from_date"})
   (42.2ms)  ALTER TABLE `dept_manager` ADD `to_date2` date NOT NULL AFTER `from_date`,LOCK=NONE
   -> 0.0428s
-- remove_column("dept_manager", "to_date")
   (46.9ms)  ALTER TABLE `dept_manager` DROP `to_date`,LOCK=NONE
   -> 0.0471s

MySQLCREATE INDEXDROP INDEXについては、ALTER文を使うように指示するオプションを追加しました。

$ ridgepole -a -c '{adapter: mysql2, database: employees}' --alter-extra="LOCK=NONE" --mysql-use-alter --dry-run
Apply `Schemafile` (dry-run)
remove_index("dept_manager", {:name=>"emp_no"})
add_index("dept_manager", ["emp_no"], {:name=>"emp_no2", :using=>:btree})

# ALTER TABLE `dept_manager` DROP INDEX `emp_no`,
# LOCK=NONE
# ALTER TABLE `dept_manager` ADD  INDEX `emp_no2` USING btree (
# `emp_no`)
# ,LOCK=NONE

$ ridgepole -a -c '{adapter: mysql2, database: employees}' --alter-extra="LOCK=NONE" --mysql-use-alter --debug
Apply `Schemafile`
...
-- remove_index("dept_manager", {:name=>"emp_no"})
   (19.2ms)  ALTER TABLE `dept_manager` DROP INDEX `emp_no`,LOCK=NONE
   -> 0.0200s
-- add_index("dept_manager", ["emp_no"], {:name=>"emp_no2", :using=>:btree})
   (23.4ms)  ALTER TABLE `dept_manager` ADD  INDEX `emp_no2` USING btree (`emp_no`),LOCK=NONE
   -> 0.0243s