ぶていのログでぶログ

思い出したが吉日

Chefのruby_blockからリソースを制御する

※ Chef + Knife-Zeroな環境を想定してこの記事を書いている。

かなり特殊な状況ではあるが、chef-clientの実行ユーザがubuntuのときにはubuntuユーザの削除を行わないということがしたい。 想定状況としては、Ubuntu VMを新たに立ち上げてbootstrapをかけ、それによりubuntuユーザの削除と別ユーザの追加*1を行うという感じ。 何も考えずに以下のようなレシピを書いても意図した動作はしない。

# knife-zeroの実行時に評価されてしまう
user = ENV['SSH_USER'] || ENV['USER']

unless user == 'ubuntu'
  # Chefの実行ユーザがubuntuでもユーザの削除が行われる
  user 'ubuntu' do
    action :remove
  end
end

コードのコメントで書いた通り、knife-zeroの実行時に評価されてしまいchef-clientの実行時に評価されないため意図しない挙動になっている…っと理解しているが間違っているかも。 これを回避するために以下のようにコードを変更した。

ruby_block 'I am ubuntu?' do
  block do
    user = ENV['SUDO_USER'] || ENV['USER']
    if user != 'ubuntu'
      resources(user: 'ubuntu').action(:remove)
    end
  end
end

user 'ubuntu' do
  action :nothing
end

ruby_blockリソースはchef-client実行時にブロック内を遅延評価してくれるリソースになっている。 このコードでは、chef-clientの実行ユーザがubuntuではないときのみubuntuリソースをremoveしている。 そして、ubuntuリソースのactionをnothingとして定義しておくことで、removeアクションをキックされない場合(つまりubuntuユーザで実行された場合)は何もせず空振りするようにしている。

node.run_stateとlazy

ruby_blockリソースの公式ページを見ていると、今回みたいな用途では node.run_stateを使うのが良さそうに思える。 しかし、どうもこの node.run_state はknife-zeroの実行時に評価されるようで最初に書いたコードのように、意図した動作をしなかった。 ↓確認したコード

ruby_block 'I am ubuntu?' do
  block do
    user = ENV['SUDO_USER'] || ENV['USER']
    if user != 'ubuntu'
      node.run_state['exec_user'] = 'ubuntu'
    end
  end
end

if node.run_state['exec_user'] == 'ubuntu'
  # node.run_state['exec_user']が常にnilになるので、ubuntuユーザを常に消そうとする。
  user 'ubuntu' do
    action :nothing
  end
end

この問題を回避するためにlazyを使えとある。 しかし、このlazyは直接actionに渡すことができないようで実行時にエラーとなってしまった。

ruby_block 'I am ubuntu?' do
  block do
    user = ENV['SUDO_USER'] || ENV['USER']
    if user != 'ubuntu'
      node.run_state['action'] = :remove
    else
      node.run_state['action'] = :nothing
    end
  end
end

user 'ubuntu' do
  action lazy { node.run_state['action'] }
end

むずかしい… Chefに詳しくないので間違ったことが書いてあるかも…

別解:あきらめる

初回のbootstrapはコケてしまう前提で、2回boostrapするという運用回避手段も考えられる。まぁそれが手間だったので今回回避策を考えたというのがこの記事の趣旨。 それ以外に、そもそもubuntuユーザを削除せず無効化・ロックなどで対応するという方法も考えられるが、その方法は私の要件には合わなかったという感じ。

cloud-initでVM作成時の処理をあれこれしているので、その中にchefの実行を入れて、その後にubuntuユーザを削除 or そもそもデフォルトユーザを作らないみたいな方法も無きにしもあらずか? 🤔

*1:実際にはSTNSの有効化