CloudFormationで使われていないExportを洗い出す
CloudFormationにおいてスタック間で値を受け渡したいときに、参照される側で出力値をエクスポートして、参照する側でFn::ImportValue
で読み込む方法があります。
エクスポートされている値は各スタック詳細の出力や[エクスポート]から見ることができます。
どのスタックからインポートされているかも確認できます。
ただし、どこからも参照されていないエクスポート値を調べるのはマネジメントコンソールでは無理なのでAPIを使って洗い出すスクリプトを書きました 。
まず最初にスタックIDとスタック名のリストを作ります。 これは、後に出てくるAPIのレスポンスにスタックIDしか含まれていないので、わかりやすいスタック名を表示するためです。
def list_stacks(result = {}, next_token = nil) sleep 1 resp = @client.list_stacks(:next_token => next_token) resp.stack_summaries.each do |stack| result[stack.stack_id] = stack.stack_name end list_stacks(result, resp.next_token) if resp.next_token result end
スタックIDをキー、スタック名をバリューとしたHashが返ります。
この手のAWSのAPIはトークンベースのページングになっているので、結果がある限りループします。ループの部分は再帰で実装しています。
あとドキュメントが見つからなかったのですが、数が多いとスロットリングが発生するので1秒起きにリクエストを送っています。スタック数、エクスポート数ともに50くらいの環境では1秒の待機でいけました。
次にエクスポート値を全て取得します。
def list_exports(result = {}, next_token = nil) sleep 1 resp = @client.list_exports(:next_token => next_token) resp.exports.each do |export| result[export.name] = { :value => export.value, :stack_id => export.exporting_stack_id } end list_exports(resp.next_token) if resp.next_token result end
これも同様にページングです。
エクスポート名をキーにした、エクスポート値と出力しているスタックIDのHashを返します。
次にエクスポートごとにインポート状態の確認を行います。
def list_imports(export_name, result = [], next_token = nil) sleep 1 resp = @client.list_imports(:export_name => export_name, :next_token => next_token) result << resp.imports list_imports(export_name, result, resp.next_token) if resp.next_token result rescue Aws::CloudFormation::Errors::ValidationError => e return [] if e.message.include?("is not imported by any stack") raise e end
戻り値は参照しているスタック名の配列です。
どこからも参照されていない場合、ValidationError
が発生するのでハンドリングしてます。 これはこのケースに限らない共通のエラークラスなので、一応詳細なエラーメッセージも判定するようにしています。
あとは、最初に取得しておいたスタックIDとスタック名のマップを使ってよしなに変換を行い、どこからもインポートされていないエクスポートをプリントして終わりです。
if __FILE__ == $0 @client = Aws::CloudFormation::Client.new stacks = list_stacks exports = list_exports puts "following exports value is not used in any stack." exports.keys.each do |key| exports[key][:importing_stacks] = list_imports(key) puts "#{key} (defined in #{stacks[exports[key][:stack_id]]})" if exports[key][:importing_stacks].size == 0 end end
思いの外面倒でした。
ソースコードはこちら github.com