読者です 読者をやめる 読者になる 読者になる

将来 knife plugin を作るつもりなら、ちゃんと生まれてくる gem の名前は考えてくるんだぞ。

knife plugin を書いたら、gem の名前が悪いせいでうまく動かなかった @tk0miya です。
gem の名前が良くないと、たとえちゃんと動くコードが合ったとしても、knife plugin として動作しません。

knife plugin の作り方(超ざっくり版)。

knife plugin を作る際は、gem の中に chef/knife/*.rb を作ります。ファイル名はなんでも構いません。
その中にコマンド名を CamelCase にしたクラスを Chef::Knife のサブクラスとして定義します。

class Chef
  class Knife
    class Bootstrap < Knife
      # ...
    end
  end
end

あとは、実行した時のアクションを run メソッドに書いていきます。

class Chef
  class Knife
    class Bootstrap < Knife
      def run
        print 'hello world'
      end
    end
  end
end

オプションを作ったり、UI (highline) を使ったり、というのは knife のサブコマンドがわかりやすいので、
実際に plugin を作る場合はこれらのコードをみてみると良いでしょう。
他、knife-soloknife-ec2 なども参考になります。

knife plugin をパッケージングする時、gem につけてはいけない名前がある

knife は RubyGems の機能を使って knife plugin を検索します。
インストールされているすべての gem から chef/knife/*.rb が含まれているものを探してくるため、
knife の中では過去のバージョンの chef を避けるコードが入っています。

コード(chef/knife/core/subcommand_loader.rb)から関連する箇所を抜き出すと

      CHEF_FILE_IN_GEM = /chef-[\d]+\.[\d]+\.[\d]+/
      CURRENT_CHEF_GEM = /chef-#{Regexp.escape(Chef::VERSION)}/

      def find_subcommands_via_rubygems
        files = Gem.find_files 'chef/knife/*.rb'
        files.reject! {|f| from_old_gem?(f) }
        ...
      end

      def from_old_gem?(path)
        path =~ CHEF_FILE_IN_GEM && path !~ CURRENT_CHEF_GEM
      end

という感じです。

詳細はコードを読んでいただくとして、ざっくりまとめるとパスが chef-¥d+.¥d+.¥d にマッチする場合に
knife plugin としての対象からはずれます。
パスには gem のパッケージ名が含まれるので、

  • gem の名前が chef で終わる
  • バージョンが 3つの数字で構成されている(x.y.z)

場合に、knife plugin としてロードされません。

僕が作っていたのは capistrano-paratrooper-chef-0.2.0 だったので、この条件にマッチして…!!

ということで、knife plugins を作るときはご注意ください。たぶんレアケースでしょうけど。