Chef実行順序の影響と回避方法まとめ


ChefでRubyコンパイル時とChefリソース収束時による実行順序の問題が生じるケースと対応をまとめておく

#問題ケース1 : システムの状態を取得し使用するロジックが、レシピではその前方にあるChefリソースの収束結果を使用できない

```ruby:
File.exist?('/tmp/test.txt')
```
```ruby:
s = `cat #{path}`
```

などの、Rubyによるシステム状態の取得は、Rubyコンパイル時に実行される為、Chef Resourceによる収束の結果を使用できない。

- 例外：Chefリソースにおけるnot_ifなどのbarrierの評価は、収束時に行われる。

#対応1a : 収束時にRubyによるシステム状態の取得を行う(後実行)

##1a-1 : ruby_blockを用いる

この場合、このruby_block内からChefのリソースを呼び出すことは、以下の対応により可能。

[chef の ruby_block 内でリソースを実行する](https://qiita.com/sonots/items/8ac33ce011bbfb210edd)

```ruby:
scope0 = self
ruby_block 'foo' do
  block do
    scope0.package 'bar'
  end
end
```

notifiesも使用可能。
使用する場合、宛先リソースの指定にはxxx.は不要。

ネストも可能。

###デメリット (あまり思いつかないが)

- ruby_blockにnot_ifなどのbarrierを付けなければ収束時に実行リソース数を0にできない。(気分的な問題)
- 単に大きなruby_blockにして全体を包んでしまうとbarrierの作成が困難に
    - 情報を入手し利用する単位でブロック化

##1a-2 : リソースの属性においてはlazy {}による遅延評価が使用可能

個人的にはfileリソースのcontent属性でよく使用している。

```ruby
file '/tmp/foo' do
  content lazy {
    %x(cat #{path} | sed 's/A/B/g')
  }
end
```
fileリソースの中で p (lazy { ... }).class などとすると、Chef::DelayedEvaluator クラスであることが判る。

以下の書式も可能

- content lazy { ... }
- content (lazy { ... })
- content (lazy do ... end)
- content(lazy { ... })
- content(lazy do ... end)

以下はエラーとなった

- content lazy do ... end






#対応1b : コンパイル時にChefリソースの実行を行う(前実行)

##Chefリソースに.run_action(:some_action)を用いる

[Chef公式：Run in Compile Phase](https://docs.chef.io/resource_common.html#run-in-compile-phase)

```ruby
resource_name 'foo' do
  action :nothing
end.run_action(:some_action)
```

この場合、デフォルトアクションの選択は無いので、:some_actionとしてアクションの明示指定が必要。
リソース本体にaction :nothingが無ければ、収束時にも実行されるので、冪等性が不十分だと問題が生じ得る。

###デメリット

- 公式にもある通り、notificationsは使えない。

- 使ったことは無いが、少なくともchefspecでは問題となる様子。
[chefspec action: nothing/end.run_action(:create) examples? #107](https://github.com/chefspec/chefspec/issues/107)




#問題ケース2 : 収束時に遅延実行されるRubyロジックにおける変数

以下などが該当

- lazy指定によるChefリソースの属性 (対応1aケースも該当)
- ruby_blockにおけるblock (対応1aケースも該当)
- Chefリソースにおけるnot_ifなどのbarrier


##Chefリソースのlazy指定による属性におけるRuby変数の値

```ruby:
val = 'true'

file '/tmp/test.txt' do
  content lazy {

    # 何らかの文字列を返す処理
    `cat #{path} | sed -e 's/^AAA=.*$/AAA=#{val}/'`  # この#{val}の値は'false'

  }
end

...

val = 'false' # 別の処理でvalに'false'を設定して使用

```

他のChefリソースの反映結果を取り込む為に遅延評価のlazyを使用する。
すると、lazyブロック内の変数に関して、コンパイル時ではなく、収束時に評価される為、上の例では AAA=false となってしまう。

この問題は、ruby_blockやnot_ifなどのバリヤーでも該当する。


##ruby_blockにおけるRuby変数の値

ruby_block内のsは遅延評価される為、最後に設定された値が使用される

```ruby
s = "abc"

ruby_block 'test' do
  block do
    %x(echo #{s} > /tmp/test.txt) # このsは"def"
  end
end

s = "def"
```



##Chefリソースでのnot_ifなどのbarrierにおけるRuby変数の値

barrierで使用されるbは遅延評価される為、最後に設定された値が使用される

```ruby
b = true

file '/tmp/test.txt' do
  content %x(date)
  not_if { b } # このbはfalse
end

b = false
```
結果としてこの例では変更される



#対応2: 対象の変数をイテレーターブロックへの変数としてしまう


以下に記載した対応が可能。
[Chefで、Ruby実行時とリソース実行時の変数の値の差を抑えるには、イテレータで変数として渡された値を使うと容易](https://qiita.com/hiroyuki_onodera/items/b3b19f4190eb0886ca34)

nodeオブジェクトから.eachにより渡せるとスマートに対応可能。

とりあえず強引かつシンプルに対応するには、例えば以下の様にする事で可能。

```ruby
["abc"].each do |s|
  ruby_block 'test' do
    block do
      %x(echo #{s} > /tmp/test.txt) # このsは"abc"
    end
  end
end

s = "def" # この影響は受けない
```

きっともっとスマートな方法があるとは思うのですが。。