はじめに
Aptには1.6からJSON-RPCを利用してフック処理を後付けで動かす仕組みがあります。 ここで利用されるJSONフックはプロトコルの仕様がドキュメント化されており、最新バージョンは0.2となっています。
今回は、このJSONフックプロトコルを利用して、事前にバグレポートがあがっているパッケージを知るためのアイデアを紹介します。
JSONフックの概要を知るには
JSONフックについては、Ubuntu Weekly Recipeの第676回 Apt CLIの操作結果をJSON RPCで受け取るという記事の 「Apt CLIの操作結果をJSON RPCで受け取る」を参照するとわかりやすいです。
ざっくりいうと、JSONフックを有効にするには次の2つが必要です。
- フックに関する設定ファイル(
/etc/apt/apt.conf.d/99-json-hooks
)を配置する - 設定ファイルに指定したスクリプトを実際に配置する
紹介した記事ではaptパッケージのテストコード由来のシェルスクリプトを紹介していました。 本記事では拡張しやすいように、Rubyスクリプトの場合の例を紹介します。
JSONフックの設定ファイルを配置する
/etc/apt/apt.conf.d/99json-hooks
に次のような内容のファイルを配置します。
AptCli::Hooks::Upgrade:: "/usr/local/bin/apt-json-hook";
フックはインストール時や、検索時に走らせることもできます。 お題に沿った内容とするため、アップグレードのときのみフックを適用してみます。
JSONフックのスクリプトを配置する
JSONフックプロトコルに関する正式なドキュメントは以下にあります。
アップグレードのときには、フックスクリプトでは次のような処理が行われます。
- helloコマンドがJSONで送られてくる
- 得られたJSONを元にフックスクリプトが対応しているバージョンを返す(0.2でよい)
org.debian.apt.hooks.install.pre-prompt
、org.debian.apt.hooks.install.package-list
、org.debian.apt.hooks.install.statistics
といったメソッドが飛んでくるので、スクリプトで状況に応じて処理をする
基本的には上記がすべてです。
apt upgradeを実行しているとき、aptクライアント側ではどのタイミングでフックが実行されているかというと次のようになっています。
org.debian.apt.hooks.install.pre-prompt
は「アップグレードパッケージを検出しています...完了」といったメッセージのあとにフックが実行されます。org.debian.apt.hooks.install.package-list
は「以下のパッケージはアップグレードされます」といったメッセージのあとにフックが実行されます。org.debian.apt.hooks.install.statistics
は「アップグレード: 2 個、新規インストール: 0 個、削除: 0 個、保留: 1 個」といったメッセージのあとにフックが実行されます。
JSONフックの仕様を踏まえると、package-list
に対応するフックスクリプトの雛形は次のようになります。
#!/usr/bin/env ruby
require "socket"
require "json"
class AptJsonHook
def run
IO.open(ENV['APT_HOOK_SOCKET'].to_i, "a+") do |io|
loop do
request = io.gets
io.gets # 後続行を読み捨てる
json = JSON.parse(request)
method = json["method"]
if method.end_with?(".bye")
exit(true)
end
if method.end_with?(".hello")
# 対応しているバージョンが0.2と通知する
io.puts({"jsonrpc" => "2.0", "result" => {"version" => "0.2"}, "id" => 0}.to_json)
io.puts
end
if method.end_with?(".install.package-list")
unless json["params"]["packages"].empty?
# ここでパッケージの情報をかき集める
# json["params"]["packages"]には次のような情報が含まれているので
# 現在インストールされているバージョンと、アップグレードにより導入されるバージョンの情報が活用できる
# [{"id"=>3790,
# "name"=>"at-spi2-common",
# "architecture"=>"amd64",
# "mode"=>"upgrade",
# "automatic"=>true,
# "versions"=>
# {"candidate"=>
# {"id"=>1455,
# "version"=>"2.49.91-2",
# "architecture"=>"all",
# "pin"=>500,
# "origins"=>[{"archive"=>"unstable", "codename"=>"sid", "origin"=>"Debian", "label"=>"Debian", "site"=>"deb.debian.org"}]},
# "install"=>
# {"id"=>1455,
# "version"=>"2.49.91-2",
# "architecture"=>"all",
# "pin"=>500,
# "origins"=>[{"archive"=>"unstable", "codename"=>"sid", "origin"=>"Debian", "label"=>"Debian", "site"=>"deb.debian.org"}]},
# "current"=>{"id"=>71628, "version"=>"2.49.91-1", "architecture"=>"all", "pin"=>100, "origins"=>[]}}},]
end
end
end
end
end
end
hook = AptJsonHook.new
hook.run
ENV["APT_HOOK_SOCKET"]
でapt
とやりとりするためのファイルディスクリプタが渡されます。
helloに対する応答はファイルディスクリタに書き込む必要がありますが、その他は特に応答を返す必要はありません。
標準出力に書き出したメッセージはそのまま端末に表示されます。
そのため、パッケージのリストを渡されたら次のようにすることで「パッケージを手動で更新しようとするときに、バグレポートがあがっているパッケージがあれば検知する」のを実現できそうです。
- パッケージのリストが渡されたら、UltimateDebianDatabaseのミラー 1 に接続し次の条件で検索する
- インストールすることで影響を受けるバグがあるかどうかは
bugs
テーブルのseverify
フィールドを参照する。(serious
やcritical
でフィルタするとよい) - インストールしようとしているパッケージで対応するものがあるかは、
bugs_packages
テーブルを参照する(package
フィールドが存在する) - 該当パッケージ・バージョンにひもづいているバグがあるかどうかは
bugs_found_in
テーブルを参照する(bugs_found_in
テーブルにはversion
フィールドが存在する)
- インストールすることで影響を受けるバグがあるかどうかは
UltimateDebianDatabaseはPostgreSQLで運用されているので、PG.connect
してSQLを投げた結果を表示することで、必要な情報を絞り込めます。
そういったパッケージが見つかったら表示するようにしておくと、よさそうです。
データベースへのアクセスのコストがあるのでレスポンスは遅くなりますが、次のように既存のバグをチェックしてその結果を表示する、というのを実現できます。
$ sudo apt upgrade -V
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています... 完了
状態情報を読み取っています... 完了
アップグレードパッケージを検出しています... 完了
以下のパッケージは保留されます:
vagrant (2.3.4+dfsg-1 => 2.3.7-1)
以下のパッケージはアップグレードされます:
libcaca0 (0.99.beta20-3 => 0.99.beta20-4)
liblog-any-perl (1.715-1 => 1.717-1)
[HOOK]: No already reported bug which affects updated packages.
アップグレード: 2 個、新規インストール: 0 個、削除: 0 個、保留: 1 個。
280 kB のアーカイブを取得する必要があります。
この操作後に 1,024 B のディスク容量が解放されます。
致命的なバグがすでに報告されているなど、いまアップグレードしてしまうとまずい 2 バージョンへアップグレードしてしまうといったやらかしを 防ぎやすくなるのではないでしょうか。(もちろん既知の不具合に限ります)
おわりに
今回は、JSONフックを活用して「パッケージを手動で更新しようとするときに、バグレポートがあがっているパッケージがあれば検知する」ための アイデアについて紹介しました。
こんなふうにすごく活用しているよ、という事例があれば@kenhysまでお知らせください。
-
UltimateDebianDatabaseとは、Debianのバグなどの情報を集約しているデータベースです。 一般向けには、そのミラーサイトが運用されています。UDD mirrorにはpsqlで接続できます。(
psql "postgresql://udd-mirror:udd-mirror@udd-mirror.debian.net/udd"
)データベースのスキーマも公開されているので、いろいろ眺めてみるのも面白いです。 ↩ -
たとえば、カーネルの更新に追従できていないdkmsモジュールを使っているのにも関わらず、カーネルをアップグレードをしてしまうと、該当カーネル向けモジュールのビルドに失敗し使えなくなります。該当モジュールがNICに必要なモジュールであったりすると、古いカーネルであえて起動しないと、ネットに繋げないということが発生します。 ↩