[Chef]猿でもわかるExecute,Script,Bashリソースの違い

ChefのResourceには、execute,script,bashという、スクリプトを実行するための似たようなリソースが3つもあります。
また、command属性を使うのかcode属性を使うのか、という選択肢もあります。
似たようなリソースが3つ、似たような属性が2つ。
同じことをやるのに何通りもの書き方ができてしまうわけです。
execute "hoge" do
  command "echo hoge"
end

sciprt "hoge" do
  code "echo hoge"
end

bash "hoge" do
  code "echo hoge"
end
ぐぐってウェブ上のサンプルコードを見るとexecuteを使っていたりscriptを使っていたり、いろんな書き方があって初心者を混乱させます。
さらにはruby/perl/pythonリソースも登場してきて深淵が深まります。

これらはいったい何が違うのでしょうか?
どう使い分ければよいのでしょうか?

結論

シェルスクリプトをbashで書くのであれば、下記はどれも変わらない。
  • "execute" リソースで "command"
  • "script" リソースで "command"
  • "script" リソースで "code"
  • "bash" リソースで "command"
  • "bash "リソースで "code"
迷ったら"bash"リソースで"code"属性を使えばよい。

解説

ソースコードを見てみるとわりと一目瞭然
公式マニュアルを見ても全然わからないのですが、ソースコードを見れば意外と簡単に仕組みがわかります。

https://github.com/opscode/chef/blob/master/lib/chef/resource/bash.rb

ちなみに私はrbenv経由でgem install chefしたので、下記のような場所にソースコードがありました。
~/.rbenv/versions/2.1.0/lib/ruby/gems/2.1.0/gems/chef-11.8.2/lib/chef/resource/execute.rb
リソースの実体は「クラス」
まず、Chefのリソースというのは実体はrubyのクラスです。
それぞれ下記のようなクラスとして定義されています。
  • Chef::Resource::Execute
  • Chef::Resource::Script
  • Chef::Resource::Bash
  • Chef::Resource::Ruby
  • Chef::Resource::Perl
継承関係
クラスの継承関係はこのようになっています。
 親                         子                      孫                        
Chef::Resource::Execute - Chef::Resource::Script - Chef::Resource::Bash
                                                 - Chef::Resource::Ruby
                                                 - Chef::Resource::Perl
Executeの子クラスがScript, Scriptの子クラスがBash/Ruby/Perlです。

クラスのコードを読んでみる

実はたったこれだけ。
Chef::Resource::Bash
class Chef
  class Resource
    class Bash < Chef::Resource::Script

      def initialize(name, run_context=nil)
        super
        @resource_name = :bash
        @interpreter = "bash"
      end

    end
  end
end
Chef::Resource::Perl
class Chef
  class Resource
    class Perl < Chef::Resource::Script

      def initialize(name, run_context=nil)
        super
        @resource_name = :perl
        @interpreter = "perl"
      end

    end
  end
end
Chef::Resource::Ruby
class Chef
  class Resource
    class Ruby < Chef::Resource::Script

      def initialize(name, run_context=nil)
        super
        @resource_name = :ruby
        @interpreter = "ruby"
      end

    end
  end
end
あっけにとられたのではないでしょうか?
bash/ruby/perl リソースは、単にインタープリタの名前が違うだけなのです。

Chef::Resource::Script
今度は親クラスの方を見てみましょう。
class Chef
  class Resource
    class Script < Chef::Resource::Execute

      identity_attr :command

      def initialize(name, run_context=nil)
        super
        @resource_name = :script
        @command = name
        @code = nil
        @interpreter = nil
        @flags = nil
      end

      def code(arg=nil)
        set_or_return(
          :code,
          arg,
          :kind_of => [ String ]
        )
      end

      def interpreter(arg=nil)
        set_or_return(
          :interpreter,
          arg,
          :kind_of => [ String ]
        )
      end

      def flags(arg=nil)
        set_or_return(
          :flags,
          arg,
          :kind_of => [ String ]
        )
      end

    end
  end
end
Chef::Resource::Executeにちょろっと毛が生えた程度のものですね。
最大の違いは、
Chef::Resource::Executeには"code"属性がない
Chef::Resource::Scriptには"code"属性がある
ということです。
codeとcommandの違い
      def command(arg=nil)
        set_or_return(
          :command,
          arg,
          :kind_of => [ String, Array ]
        )
      end
      def code(arg=nil)
        set_or_return(
          :code,
          arg,
          :kind_of => [ String ]
        )
      end
commandは複数渡せる、codeは複数渡せない、くらいの違いでしょうか。

まとめ

Chefを使っていてもやもやしたらソースコードを見てみましょう。
意外な発見があります。

参考

Chef | resources | execute と script と bashの違いを知るためにオープンソースのコードリーディングをする - tbpg's programming memo
カテゴリ:

人気記事