actions/setup-javaにcache-dependency-pathオプションを実装しました

GitHub Actionsで公式のアクションを利用するとき、依存パッケージを適切にキャッシュすることが大切です。 setup-node・setup-python・setup-javaにはcacheオプションがあり、指定したパッケージマネージャーに応じてキャッシュしてくれます。 setup-goの場合はcacheオプションはブール値ですが、デフォルトが有効になっているのでこれを指定しなくてもキャッシュしてくれます。 これらのアクションは、リポジトリの依存管理ファイルの内容をキャッシュのキーとして保存します。

例えば、setup-nodeでcache: npmを指定するとリポジトリルートのpackage-lock.jsonファイルの内容を元にキャッシュのキーを計算します。 しかし、モノレポ構成のリポジトリでは依存管理ファイルが各パッケージのディレクトリに配置されているため、ルートのファイルしか見ないのは不十分です。 このようなケースで役に立つのがcache-dependency-pathオプションです。

- uses: actions/setup-node@v4
  with:
    node-version-file: .node-version
    cache: npm
    cache-dependency-path: subdir/package-lock.json

setup-go, setup-pythonにも同じオプションがあり、モノレポ構成でのキャッシュの挙動を制御できるようになっています。

さて、タイトルのsetup-javaの話です。 リポジトリルートの依存管理ファイルしか見ないsetup-nodeやsetup-goとは異なり、setup-javaリポジトリ全体から検索してキャッシュキーを計算します。 これではキャッシュが複数のプロジェクト間で共有されてしまう上に、一つのプロジェクトの依存の変更が全てのプロジェクトのキャッシュに影響してしまいます。 しかも、cache-dependency-pathオプションがsetup-javaにはありませんでした。

仕事で使う上でだいぶ困っていたのと、他の公式アクションには揃っているオプションがsetup-javaにだけない理由はないと感じたので、サクッと実装してみました。 今年の六月のことです。 github.com しばらく放置されてダメかと思っていましたが、二週間前にようやくレビューしていただき、先日ようやくマージされました。 v4としてリリースされています。

github.com

使い方は、他のactionのcache-dependency-pathと同じです。 サブプロジェクトのGradleファイルを指定するとその内容を元にキャッシュキーが計算されます。

- uses: actions/checkout@v4
- uses: actions/setup-java@v4
  with:
    distribution: corretto
    java-version: '17'
    cache: gradle
    cache-dependency-path: |
      sub-project/*.gradle*
      sub-project/**/gradle-wrapper.properties

今回、公式のactionに初めてコントリビュートできました。 TypeScriptで書かれていてコントリビュートしやすいですし、actionのE2Eのやり方なども参考になりました。 個人的にはまだ大きなactionを実装したことがないので、何か作ってみたいと思いました。

RenovateでGitHub成果物のチェックサムを更新する

開発やビルドに必要なツールをインストールするとき、特にビルド済みの実行ファイルをインターネットからダウンロードするとき、セキュリティインシデントを防ぐためにはアーティファクトを信頼しても良いか確認しなくてはいけません。 もしGnuPGで署名されていればそれを使うことになりますが、正直に言うと開発者にとってもユーザーにとっても手間がかかることが多いです。 一般によく取られている方法が、チェックサムファイルをアーティファクトと一緒に公開することです。 ユーザーは、sha256sumコマンドなどでアーティファクトチェックサムを公開されているチェックサムと比較し、改竄されていないことを確認できます。

中間者攻撃やソーシャルハッキングによるアーティファクトの改竄などのシナリオを想定すると、アーティファクトと同じドメインで公開されているチェックサムファイルを、同じスクリプトのなかでダウンロードして使うことはできません。 しかし、ビルドするスクリプトの中で人間が目でチェックサムを確認するわけにはいきませんから、チェックサムの確認コマンドを実装する時は公開されているチェックサムをそのままスクリプトに書くことになります。 Dockerfileを例にしますが、シェルスクリプトでも同様です。

ARG GRPC_HEALTH_PROBE_VERSION=v0.4.18
ARG GRPC_HEALTH_PROBE_SHA256SUM=0437836b49728bd2ca0da124a8cd005679dabf85a51e1ac429af7d968f932442
RUN curl -fsSL -o /usr/local/bin/grpc_health_probe "https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/${GRPC_HEALTH_PROBE_VERSION}/grpc_health_probe-linux-amd64" \
 && echo "${GRPC_HEALTH_PROBE_SHA256SUM} /usr/local/bin/grpc_health_probe" | sha256sum -c \
 && chmod +x /usr/local/bin/grpc_health_probe

このようにしておけば、中間者がアーティファクトを差し替えたとしてもビルドが落ちるだけですし、悪意を持つ者がバージョンとチェックサムを変更したとしても、改竄されたアーティファクトをダウンロードしたビルドを全てリストアップできます。

一方で、私たちは依存するソフトウェアを短いサイクルで更新していかなければなりません。 そうしなければ、ソフトウェアはたちまち古くなってしまい、バージョンアップに多大な労力が必要になるだけでなく、セキュリティリスクも高まってしまいます。 そこでRenovatedependabotのようなツールが役に立ちます。 実際、RenovateでDockerfileの中で依存しているソフトウェアのバージョンを更新するのは、regexManagers:dockerfileVersionsプリセットを使って簡単に実現できます。 個人的にはこのプリセットの名前がとても紛らわしいと感じるのですが、Dockerfileの中のバージョンぽいENVやARGを更新してくれるというものです。

  "extends": [
    "config:base",
    "regexManagers:dockerfileVersions",
  ],
  "enabledManagers": ["regex", etc...
# renovate: datasource=github-releases depName=grpc-ecosystem/grpc-health-probe
ARG GRPC_HEALTH_PROBE_VERSION=v0.4.18

RenovateはGitHubの該当するリポジトリのreleasesを確認して、バージョンを上げるPRを作成してくれます。

しかし、ここであなたはそのPRを手で修正しないといけないことに気が付きます。

  # renovate: datasource=github-releases depName=grpc-ecosystem/grpc-health-probe
- ARG GRPC_HEALTH_PROBE_VERSION=v0.4.18
+ ARG GRPC_HEALTH_PROBE_VERSION=v0.4.19
  ARG GRPC_HEALTH_PROBE_SHA256SUM=0437836b49728bd2ca0da124a8cd005679dabf85a51e1ac429af7d968f932442

チェックサムが更新されていません。 これは困りました。 人間がいちいちチェックサムファイルをダウンロードして更新してコミットするのは面倒です。 かと言って、これまでに説明してきたように、チェックサムファイルをビルドスクリプトの中でダウンロードするのは何のセキュリティ対策にもなっていません。

こういうシナリオのために、Renovateは github-release-attachments というデータソースを持っています。 このデータソースは、digestとしてアーティファクトチェックサムを想定しています (github-releasesのdigestはコミットのハッシュです)。

# renovate: datasource=github-release-attachments depName=grpc-ecosystem/grpc-health-probe
ARG GRPC_HEALTH_PROBE_VERSION=v0.4.18
ARG GRPC_HEALTH_PROBE_SHA256SUM=0437836b49728bd2ca0da124a8cd005679dabf85a51e1ac429af7d968f932442

残念ながらプリセットの regexManagers:dockerfileVersions は、現在はdigestまでは更新してくれません。 仕方がないのでプリセットのパターンを参考にしながら、以下のような設定を書くことになります。

  "regexManagers": [
    {
      "fileMatch": ["(^|/)Dockerfile$"],
      "matchStrings": [
        "# renovate: datasource=(?<datasource>[a-z-]+?) depName=(?<depName>[^\\s]+?)(?: versioning=(?<versioning>[^\\s]+?))?\\s+(?:ENV|ARG)\\s+[A-Za-z0-9_]+?_VERSION[ =]\"?(?<currentValue>.+?)\"?\\s+(?:(?:ENV|ARG)\\s+[A-Za-z0-9_]+?_SHA256SUM[ =]\"?(?<currentDigest>[a-f0-9]+?)\"?\\s)?"
      ]
    },

これでようやく、Renovateはバージョンとともにチェックサムも更新してくれるようになりました。

  # renovate: datasource=github-release-attachments depName=grpc-ecosystem/grpc-health-probe
- ARG GRPC_HEALTH_PROBE_VERSION=v0.4.18
- ARG GRPC_HEALTH_PROBE_SHA256SUM=0437836b49728bd2ca0da124a8cd005679dabf85a51e1ac429af7d968f932442
+ ARG GRPC_HEALTH_PROBE_VERSION=v0.4.19
+ ARG GRPC_HEALTH_PROBE_SHA256SUM=0f46d50fb7220dcf4cbf8861b394be9ebdfba5d7712f3900f0f4d6c38843cbcf

さて、Renovateはどのように新しいチェックサムを知るのでしょうか。 後続のコマンドからアーティファクトのURLを調べて、実際にダウンロードしてチェックサムを計算しているわけではありません。 それは複雑すぎますし、Renovateの更新する処理で攻撃のリスクが上がります。 そもそもソフトウェアの提供者が公開していないものをチェックサムとして使うのはナンセンスです。

Renovateのgithub-release-attachmentsは、チェックサムファイルがアーティファクトとして公開されていることを想定しています。 そして GitHubのreleasesのアーティファクトの中から"チェックサムファイルっぽい"ファイルを探し、今のチェックサムに該当するファイル名を探します (詳しくは実装を参照ください。ヒューリスティックな方法に見えるかもしれませんが、そもそもRenovate自体がヒューリスティックの塊だと思います)。 ユーザーが設定しているチェックサムに対応するファイルの名前が分かれば、新しいチェックサムファイルをダウンロードして新しいチェックサムを知ることができます。

依存するソフトウェアをダウンロードするとき、アーティファクトが改竄されていないことを確認するのは必要なことです。 どのような攻撃に対して有効な対策なのか、そして仮にビルドを許したとしてもインシデントが起きた時に追跡できるか、こういうことを普段から意識するのは大切なことです。 一方で、依存するソフトウェアは常に更新していくべきですし、そのために割く労力は必要最低限にしたいものです。 この記事では、アーティファクトチェックサムの確認をしながら、Renovateにより自動で更新していく方法を紹介しました。

最後にjqをDockerイメージにインストールする設定を置いておきます。 きっと誰かの役に立つことでしょう。

# renovate: datasource=github-release-attachments depName=jqlang/jq versioning=regex:^jq-(?<major>\d+)\.(?<minor>\d+)(\.(?<patch>\d+))?$
ARG JQ_VERSION=jq-1.7
ARG JQ_SHA256SUM=2f312b9587b1c1eddf3a53f9a0b7d276b9b7b94576c85bda22808ca950569716
RUN curl -fsSL -o /usr/local/bin/jq "https://github.com/jqlang/jq/releases/download/${JQ_VERSION}/jq-linux-amd64" \
 && echo "${JQ_SHA256SUM} /usr/local/bin/jq" | sha256sum -c \
 && chmod +x /usr/local/bin/jq

jq 1.7をリリースしました

jqがjqlang organizationに移譲され、数名の新たなメンテナーを入れた開発体制に移行してから三か月が経ちました。 私にとってこの三か月はとても濃厚で、これまでのOSS活動の中でも特に大変な期間でした。 itchyny.hatenablog.com github.com

リポジトリの管理権限をいただいてからまずやったことは、既存のissueやPRの整理でした。 500ほどのissueとPRに目を通し、ラベルをつけて、解決済みのものを閉じて、直近で入れたいものを独断でリリースマイルストーンに入れていきました。 この整理がついた頃には他のメンテナの活動も活発になり、私の作ったマイルストーンのissueやPRを確認してくれました。

そして先日、ようやく1.7をリリースしました。 1.6から実に五年弱、一時は開発が完全に止まってしまいプロジェクトの存続を危ぶむ声も上がるような状況からの再起をかけたリリースになりました。 github.com

詳しくはリリースノートを参照していただければ良いと思いますが、この記事でも主な変更点をあげておきましょう。

数値の精度の保持

JSONの中やjqクエリの数値の精度を保持し、比較したりソートしたりできるようになりました。 数値形式のIDやREST APIナノ秒など、JSONの中の数値が浮動小数点として解釈されたくない場面で役に立ちます。 この機能は2019年10月には実装されてマージされていましたが、正式にリリースがされていなかったため何度も新しいissueが立てられる問題でした。 なお、単項のマイナスや二項演算子fabsroundなどの数学的なフィルターが絡むと浮動小数点の精度に落ちます。

 $ echo '{"id":100000000000000000}' | jq .
{
  "id": 100000000000000000
}
 $ jq -n -c '[100000000000000001, 100000000000000003, 100000000000000004, 100000000000000002] | sort'
[100000000000000001,100000000000000002,100000000000000003,100000000000000004]

github.com

コマンド出力の色

暗いテーマの端末にてnullの色が見にくいという問題を解消しました。 これまではデフォルトでBold Black (\e[1;30m) が使われていましたが、Bright Black (\e[0;90m) に変更しました。 デフォルト色を変更するというのは難しい判断でしたが、設定のない状態で多くの人に快適に使ってもらえるべきと考えて変更しました。 ⭐️

github.com

オブジェクトのキーの色をJQ_COLORS環境変数で設定できるようになりました。 これまで、nullやBoolean、数値や文字列などの色はJQ_COLORS環境変数によって設定が可能でしたが、オブジェクトのキーだけは設定できないようになっていました。 JSONの型と色付けの設定が一対一対応しているという実装上の制約がありましたが、オブジェクトのキーの色を変更したいという要望は多く、JQ_COLORSの最後の要素で指定できるように拡張しました。 ⭐️

github.com

さらにNO_COLOR 環境変数が設定されていたら色をつけずに出力するようになりました。 多くのコマンドラインツールがサポートしている環境変数であり、色のついた出力を望まないユーザーには便利な機能です。 なお --color-output (-C) が指定された場合はそちらが優先されます。 ⭐️

github.com no-color.org

より多くのプラットフォームをサポート

リリースのビルド済み実行ファイルとして、より多くのプラットフォームのためにクロスビルドして公開するようになりました。 以下のアーキテクチャのビルド済み実行ファイルを公開しています。

  • Linux: amd64, arm64, armel, armhf, i386, mips, mips64, mips64el, mips64r6, mips64r6el, mipsel, mipsr6, mipsr6el, powerpc, ppc64el, riscv64, s390x
  • macOS: amd64, arm64
  • Windows: i386, amd64

github.com

さらに、GitHub Container Registry ghcr.io/jqlang/jq にDockerイメージをpushするようになりました。 マルチアーキテクチャ (386, amd64, arm64, mips64le, ppc64le, riscv64, s390x) のイメージをGitHub Actionsでビルドしています。 ⭐️ github.com

他の人に助言をいただいたのですが、QEMUによるエミュレーションの上でビルドするのは時間がかかるため、ビルド済みのバイナリをscratchイメージにCOPYするだけになっています。 ⭐️ github.com

言語の改善

|= emptyで配列の要素の削除

jqには empty という特殊なフィルターがあり、空のストリームを表します。 更新代入オペレーター (|=) の右辺に使うことでオブジェクトのフィールドを削除できますが、配列の要素に使うと意図しない要素を削除してしまうバグがありました。 削除処理を順番に行っていることでインデックスがズレて行ってしまうのが問題でした。 削除対象のインデックスを集めて最後にまとめて削除することで解決しました。 ⭐️

 $ jq -n '{foo: 1, bar: 2, baz: 3} | .bar |= empty'
{
  "foo": 1,
  "baz": 3
}
 $ jq -n '[range(5)] | (.[] | select(. % 2 == 0)) |= empty' # 1.6 では [1,2,4]
[
  1,
  3
]

ただし、普通は del/1 を使うのが良いと思います。

github.com

try-catchのバグ修正

jq 1.6では、try-catch を更新代入オペレーターの右辺に使うと意図しない挙動をすることがありました。 六件報告されていたこの問題は、エラーハンドリングの内部処理のバグに起因するものでした。

 $ jq -n '["true","[]","foo","}{"] | .[] |= try fromjson catch "x"' # 1.6 では ["x","x","x","x"]
[
  true,
  [],
  "x",
  "x"
]
 $ jq -n '["1", "2a", "3", 4] | .[] |= tonumber? // .' # 1.6 では ["2a",4]
[
  1,
  3,
  4
]

github.com

オブジェクトのキーとして変数をサポート

オブジェクトのキーに変数をそのまま使えるようになりました。 これまでは括弧が必要でした。

 $ jq -n '"test" as $key | {$key:42}' # == {($key):42}
{
  "test": 42
}
 $ jq -n '"test" as $key | {$key}' # == {key: $key} なので注意
{
  "key": "test"
}
 $ jq -n '"test" as $key | {$key:$key}'
{
  "test": "test"
}
 $ jq -n '{key:"test"} | {key}' # == {key: .key}
{
  "key": "test"
}

ドットの連鎖をサポート

オブジェクトや配列のインデキシング (.["foo"], .[0]) やイテレータ (.[], .[]?) を続けるときに、.を省略せずに書くことが可能になりました。

 $ jq -n '{foo:[]} | .foo.[0], .foo.["foo"]?, .foo.[], .foo.[]?'
null

# これまでは . を書かない構文のみサポートしていた
 $ jq -n '{foo:[]} | .foo[0], .foo["foo"]?, .foo[], .foo[]?'
null

else節のないif式をサポート

if式のelse節を省略できるようになりました。 省略した場合は else . と同じです。 jqのように常に入力値のある言語での"何もしない"というのは入力値をそのまま返す操作だからです。

 $ jq -n '5,15 | if . < 10 then . * 2 end'
10
15

NULセパレータの出力オプション

jqは通常出力を改行区切りで出力します。 これは文字列をクォートせずに出力する--raw-output (-r)オプションでも同様ですが、文字列が改行を含みうる場合には他のコマンド (read, xargs) に渡すのが安全ではないケースがあります。

この問題を解決するために、--raw-output0オプションが導入されました。 文字列をクォートせずに出力する上に、セパレータとしてNUL (\x00)を使います。 jqの出力をread -d ''xargs -0で処理するときに便利です。

 $ jq -n --raw-output0 '"a b c", "d\ne\nf"' | xargs -0 printf '[%s]\n'
[a b c]
[d
e
f]

# 文字列自体がNULを含む場合はエラー
 $ printf '{"foo":"foo\\u0000bar"}' | ./jq --raw-output0 .foo
jq: error (at <stdin>:0): Cannot dump a string containing NUL with --raw-output0 option

新しいビルトインフィルター

以下のビルトインフィルターが追加されました。

  • pick/1: 引数で辿った構造を抜き出します。以前のバージョンより {foo}{ foo: .foo } と同じクエリですが、{ foo: { bar: .foo.bar } } に相当する簡潔な記述方法がありませんでした。今後は pick(.foo.bar) と簡潔に書けるようになります。さらに、pick(.. | strings | select(contains("GITHUB_")))といった高度な利用方法もあります。
  • abs/0: 数値の絶対値を計算します。fabslengthなどの同様のフィルターはすでに存在していますが、よりわかりやすい名前とシンプルな実装で導入されました。現在は精度が浮動小数点になりますが、将来的に整数の精度を維持する実装になる可能性があります。
  • debug/1: debug/0と同様、標準エラー出力デバッグ出力を出すフィルターですが、より柔軟に出力を制御できます。例えば debug("id: \(.id), name: \(.name)") のように必要なフィールドのみをデバッグすることが可能です。
  • scan/2: scan/1と同様、入力文字列から正規表現にマッチする部分文字列を抜き出しますが、正規表現のフラグに対応しています。 ⭐️

ウェブサイトのリデザイン

公式のウェブサイト https://jqlang.github.io/jq/ のデザインをリニューアルしました。 最初にBootstrap 3で作られて以来、誰も手をつけていなかったウェブサイトのデザインを一新して、モダンなサイトに作り替えました。 もちろんダークモードやレスポンシブの対応、svgアイコンの作成なども行いました。 ⭐️

github.com

まとめ

五年という歳月を経て、jqの新しいバージョンがリリースされました。 この記事に書いたもの以外にもメモリーリークやセグフォを含む様々なバグが修正されています。 OSS-Fuzzが導入され、スタック保護が有効になるなど、セキュリティ面でも様々な改善が導入されました。 私も⭐️をつけた項目を実装したり、他の人のPRをレビューしたりして、新たなリリースに貢献しました。

jqは現代の開発になくてはならないツールになりました。 私も開発プロセスのあらゆる場面で依存していて、メンテナンスが滞ってセキュリティリスクが高まっても代替を探すのが困難です (gojqに切り替えれば良い説もあります)。 世界中に多くのユーザーがいる中で、バグに見える挙動にも依存しているスクリプトがあることをいつも想像しなくてはいけません。 このように"安定している"ことが期待されるツールを問題なくリリースするのは、かなり緊張感のある作業でした。 二回のRCバージョンを出してユーザーにテストを促しましたが、それでもリリース後に細々としたバグ報告がされています。 新たなメンテナ体制の中で様々なことを学びつつ、この特別なツールの開発を続けていきたいと思っています。

github.com

jq が jqlang organization に移譲されました

JSONを操作するコマンドラインツールであるjqは、これまでオリジナル作者であるStephen Dolan氏 (@stedolan)のリポジトリ(github.com/stedolan/jq)で管理されていました。 メンテナンスはNico Williams氏 (@nicowilliams)とWilliam Langford氏 (@wtlangford)の二名が行なっていましたが、近年は活動が減っておりメンテナンスが滞っていることが度々指摘されていました。 最新のリリースは2018年11月に行われた1.6であり、その後に様々なバグ修正やパフォーマンス改善、新機能の実装が行われているのにリリースされておらず、またissueやPRも放置されがちになっていました。 さらにCI (AppVeyor)は常に落ちるので、簡単なドキュメント修正でもCIが通らず苦情が来る、数か月放置されたPRは作った人が諦めて無言で閉じられるといった状況でした。

jqは広く使われている有用なツールであるにも関わらず、メンテナンスがこのように滞っていることを不安に思う人は多く、そういう声は多く寄せられていました。 しかし、メンテナの二人が忙しく時間が取れないことや、オリジナル作者と連絡がつかないことなどもあり、うまく話が進みませんでした。 github.com

こういう状況ではありますが、メンテナのNico Williams氏は去年末からorg移行を構想していたようで、jqlang organizationを作成したり、リポジトリをforkしてGitHub Actionsを整備したりしていました。 彼はオリジナル作者の意思を確認できるまでは元のリポジトリを公式としたままgithub.com/jqlang/jqで開発を進めて必要なものをオリジナルのリポジトリにバックポートすることを考えていたようなのですが、先週オリジナルの作者であるStephen Dolan氏が突然降臨して、リポジトリを完全に移譲するという意思を示しました。 github.com

ここからの展開は早く、当初懸念されていたGitHub Pagesのjq manualのリダイレクト方法も編み出されて、リポジトリのjqlang organizationへの移譲(transfer)があっという間に完了しました。 古いリポジトリのissueやPR、リリースのURL、マニュアルなど全て新しいリポジトリにリダイレクトされています (一部の実行ファイルのダウンロードURLのリダイレクトは対応できなかったようなので、失敗するようになったらGitHub Releasesから直接ダウンロードしてください)。 とにかく晴れて、jqは個人のリポジトリからjqlang organizationの管理に移行したのです。

organization管理下への移行というのは形式的なものに見えるかもしれませんが、新たに六名がメンテナとしてメンバーに加えられ、持続的なメンテナンスを目的としています。 そしてありがたいことに、他薦と自薦により私もメンテナとして招待されて参加しました。 gojqの作者としてjqの仕様には詳しいと自負しておりますが、jqのメンテナになれたことはとても嬉しかったです。 さらに、メンテナ同士は素早いコミュニケーション (とはいえ時差はあるのですが) のために、Discordでチャットできるようになりました。 滞ってしまったメンテナンスをどう立て直すか、次のリリースに向けた議論を進めていきたいと思います。

jqの開発には、C言語はもちろんのこと、パッケージング、マルチプラットフォームなCIなど様々な知識が求められます。 jqの持続的な開発のために、自分にできることから貢献していこうと思っています。

\広告です/

YAPC::Kyoto 2023 に参加した #yapcjapan

yapcjapan.org 知り合いも多いし会場が家から徒歩圏内ということもあって参加せざるを得ませんでした。 前職はてなのプチ同窓会があちこちで発生して、色々な方と話ができて良かったです。

しかし、前職にいた時は当たり前のようにできていたようなエンジニアリング仕草や開発マインドができなくなっていたことを改めて痛感してしまいました。 今年に入ってからアウトプットが低調気味ですし、本来ならOSSにすると良さそうだがなと思いながら社内リポジトリGitHub Actionsを作ることもあったことを思い出しました。 YAPCに参加したことは良い刺激になりましたし、こういう元々自分が得意だったはずの領域を転職後チームに文化を持ち込めなかったことを反省しています。 同年代のOBもそれぞれの転職先で活躍されているようで、自分も頑張らなければならぬなとなりました。

Perl companyにいたのにおそらく初めてのYAPC参加であり、著名なエンジニアたちが集っていることに興奮する気持ちを抑えつつ (発表中にツッコミをしているこの方がdankogai氏か!)、勉強になる発表もあって良かったです。 onkさんのORMの話もmoznionさんの廃墟の話も、macopyさんのデプロイ今昔物語も、yusukebeさんのhonoの発表もどれも面白かったです。 Helpfeelさんのランチセッションも興味深く聞いていました。 参加できなかった発表の中でも面白そうなものが多かったので、動画の公開が待ち遠しいです。

onishiさんのキーノートはあまりにも良く、心揺さぶられるものがありました。 とてもよくできたストーリーでしたが、でもonishiさん、onishiさんはとっくに主役ですよ。 このストーリーはもう最初からメインキャラのシナリオじゃないですかね。 個性あふれる様々なキャラがonishiさんの元に集まってきて、その先頭に立ち続けている。 面白い景色をまた見せて欲しいです。

運営スタッフの皆様、登壇者の皆様、本当にありがとうございます。 そしてお疲れ様でした。 このような素晴らしい場に参加できて良かったです。 学生の参加者も多かったし、世代交代の風を少し感じられたのがいいですね。 広島も行けるかな。楽しみです。