pure mrubyで実装されたItamae「itamae-mruby」を作った

itamae-goを作り直してitamae-mrubyを作った

先週Goからmrubyを使ってRubyなしでItamaeレシピを実行できる「itamae-go」を作ったんだけど、全く同じコンセプトの、RubyなしでItamaeレシピを実行できる「itamae-mruby」を作った。

github.com

itamae-goの問題点

mrubyは組み込み言語だしこれは本来想定された使い方であり、go-mrubyの実用的な例として普通に作ってよかったと思っているけど、ことItamaeを実装することに関しては以下のような問題があった。

  • レシピを読む部分以外をGoで実装していたので、specinfraのコードの移植に手間がかかる
  • 主にstandaloneなバイナリを吐く目的にGoが使われているが、mruby-cliでもできるのでGoを使っているメリットがそれほどなく、2つの言語をブリッジするコードを書く労力に見合わない

僕としてはRubyなしで動くItamaeは環境構築の用途にやっぱり欲しいし、ちゃんとメンテし続けたいので保守がしやすいように作り直した。

mruby-cliとは

Goと違ってmruby自体には自分が書いたスクリプトをstandaloneなバイナリにする手段はない(多分)。そこで、mrubyのC APIを使ってmrubyで書いたコードの__main__メソッドにargvを渡して呼び出すCのコードと、それをクロスコンパイルするDocker環境を提供しているのがmruby-cliである。 なので、mruby-cliを使えばRubyの存在に依存しないバイナリをRubyで書くことができる。

itamae-mrubyの現状

  • 現時点で基本的な機能はitamae-goのスーパーセット
    • mruby-yamlのクロスコンパイルがうまくいかないので一旦--node-yamlは外している
  • OSX, Arch, Debian, Ubuntuに加えCentOS, Gentooとかもサポートするようにした
  • 多分動くけどまだちゃんと検証できてない

itamae-mrubyの設計

ryotaraiさんがmrubyで実装した奴をItamae 2にしようとか言ってたので微妙に手間をかけて作っている。まあそんな簡単には置き替えられないと思うけど。

  • resourceからresourceを実行する部分を分離し役割を減らしている
  • ryotaraiさんのアイデアで、actionを実行するのではなく事前状態と事後状態を受けて適用するようにしてみている
  • specinfraは移植しやすいよう完全にそのまま

mrubyだけでCLIを実装した知見

mrubyはまだまだ未整備な部分が多くフロンティア感がある。普通にRubyで実装するのとはいろいろ違うところがあったので、そのへんの感想を書いておく。

Rubyにはあるがmrubyに存在しなかったもの

rubygemsが使えないというのがやっぱり大きい。そして標準ライブラリは大体ないし、本家だと言語機能になっている部分がないこともあり、やっていく必要がある。以下に私のやっていきにより生まれたものを書いておく。

mruby-hashie

ItamaeはHashie::Mashをガッツリ使っているし、社内のレシピでも使われてるのでmrbgemにしておいた。元のコードがMIT Licenseなので、ライセンス表記やauthorsとともに実装をいただいたらほとんどそのまま *1 で動いた。なお、Hashie::Mashしかない。

mruby-shellwords

標準ライブラリ。かなりコマンドをガチャガチャやるので普通に必要だった。Itamaeで必要だったメソッドはそのままのコードで動いた。

mruby-open3

標準ライブラリなんだけど、これを作るのはコピペでは全然動かないし結構大変。まず既存のmrbgemにspawnがないので、それを自分で書く必要がある。spawnは本来mruby-processとかで実装すべきだけど、spawnのオプションをいろいろサポートするのは実装するのは割と大変なので、outとerrオプションを必ずredirectするインチキspawnを内蔵している。

意外とmrubyにも存在したもの

Rubyに比べてしまうともの足りなく感じるけど、mrbgem自体は結構ある。個人的にはmruby-threadとかmruby-tempfileがあるのが助かった。

GitHub - mruby/mgem-list: A list of all GEMs for mruby to be managed by mgem

ファイル名順にソースが読み込まれる

Dir.glob("#{dir}/mrblib/**/*.rb").sortの順に読まれるので、主に継承が必要な時にファイル名の先頭に数字をつけるみたいなことをやる必要がある。

mruby-requireを使うと綺麗に書けるんだけど、実行時にファイルを探しにいくのでワンバイナリを作る用途では使えなそう。

bundle gemコマンドみたいな奴

あった。便利。

github.com

まとめ

Rubyなしで実行したいコマンド作る用途にもmrubyは便利なので、この知見を参考にmrubyを使う人が増えてmrubyのエコシステムがよりよくなってほしい。あとitamae-mrubyもよろしくね

*1:&:sym でto_procする奴がなんか動かなかった。mirbでは動くので多分サポートはされてるんだけどmrbgemの中だとなぜか…。あとでちゃんと調査する