[インデックス 10502] ファイルの概要
このコミットは、Go言語プロジェクトのビルドダッシュボードにおける、ビルダ(自動ビルドシステム)が利用するバックエンド機能の実装と、それに関連するテストの追加を目的としています。具体的には、コミット情報、ビルド結果、タグ、ビルドログなどをApp EngineのDatastoreに保存・管理し、ビルダからのリクエストに応じて適切な情報を提供するAPIエンドポイント群が実装されています。
コミット
commit 49dfaad870208969ff3129ed1e1bda8f12302c03
Author: Andrew Gerrand <adg@golang.org>
Date: Fri Nov 25 12:53:05 2011 +1100
dashboard: builder-facing implementation and tests
R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5431048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/49dfaad870208969ff3129ed1e1bda8f12302c03
元コミット内容
dashboard: builder-facing implementation and tests
このコミットは、Goプロジェクトのダッシュボードにおいて、ビルダ(自動ビルドを実行するマシンやプロセス)が利用する機能の実装と、そのテストコードを追加するものです。
変更の背景
Go言語プロジェクトのような大規模なオープンソースプロジェクトでは、様々なプラットフォームやアーキテクチャでのビルドとテストの継続的な実行が不可欠です。これを実現するためには、各ビルダが自身の作業状況(どのコミットをビルドしたか、結果はどうだったか、ログは何かなど)を中央のシステムに報告し、その情報を集約・可視化する「ビルドダッシュボード」が必要です。
このコミットは、そのビルドダッシュボードのバックエンド部分、特にビルダからのデータ受信と管理を行うAPIとデータモデルをGoogle App Engine上に構築することを目的としています。これにより、ビルダはHTTPリクエストを通じてコミット情報、ビルド結果、ログなどをダッシュボードに送信できるようになり、ダッシュボードはこれらの情報を永続化し、後続の処理や表示に利用できるようになります。
前提知識の解説
Google App Engine (GAE)
Google App Engineは、Googleが提供するPlatform as a Service (PaaS) です。開発者はアプリケーションコードを記述し、GAEにデプロイするだけで、インフラの管理をGoogleに任せることができます。スケーラビリティ、信頼性、セキュリティが組み込まれており、Go言語を含む複数の言語をサポートしています。このコミットでは、Go言語で書かれたApp Engineアプリケーションとしてダッシュボードのバックエンドが構築されています。
Google Cloud Datastore (旧称: App Engine Datastore)
Google Cloud Datastoreは、Google Cloud Platformが提供するNoSQLドキュメントデータベースです。GAEアプリケーションの永続化層としてよく利用されます。Datastoreは、エンティティ(データレコード)、キー(エンティティを一意に識別するID)、エンティティグループ(関連するエンティティの集合で、トランザクションの単位となる)といった概念を持ちます。このコミットでは、Package、Commit、Result、Log、TagといったGoの構造体がDatastoreのエンティティとしてマッピングされ、データの保存と取得が行われています。
Go言語
GoはGoogleによって開発された静的型付け、コンパイル型のプログラミング言語です。シンプルさ、効率性、並行処理のサポートが特徴です。このコミットのコードは全てGo言語で書かれており、net/httpパッケージを使ったHTTPハンドラの定義や、appengine、appengine/datastoreパッケージを使ったGAE固有の機能利用が見られます。
継続的インテグレーション (CI) とビルドダッシュボード
継続的インテグレーション (CI) は、開発者がコード変更を頻繁にメインブランチにマージし、自動化されたビルドとテストを実行するソフトウェア開発プラクティスです。ビルドダッシュボードは、CIシステムの一部として、様々なビルドジョブのステータス、テスト結果、コードカバレッジなどを一元的に表示するウェブインターフェースです。これにより、開発チームはプロジェクトの健全性を常に把握し、問題があれば早期に発見・修正できます。このコミットは、このビルドダッシュボードのデータ収集部分を担っています。
技術的詳細
このコミットは、Go App Engineアプリケーションとして動作するビルドダッシュボードのバックエンドAPIを実装しています。
app.yaml の変更
app.yaml はApp Engineアプリケーションの設定ファイルです。このコミットでは、以下の新しいURLハンドラが追加されています。
/log/.+: ビルドログの取得エンドポイント。/buildtest: テスト用のエンドポイントで、管理者ログインが必要。- 既存の
/commit,/tag,/todo,/resultエンドポイントも_go_appスクリプトにルーティングされています。
misc/dashboard/app/build/build.go の変更
このファイルは、ダッシュボードの主要なデータモデルとAPIハンドラを含んでいます。
Datastoreエンティティの定義と変更
Package: ビルド対象のパッケージ(例: Go本体、その他のGoリポジトリ)を表す。NextNumフィールドが追加され、次のコミットに割り当てる連番を管理するようになりました。Commit: 特定のパッケージにおけるコミットを表す。Numフィールドが追加され、パッケージ内で単調増加するコミット番号が割り当てられるようになりました。Keyメソッドの変更:Packageエンティティを親とするエンティティグループ内にCommitエンティティが作成されるようになり、トランザクションの整合性が向上しました。キーの形式もPackagePath|Hashに変更されています。Valid(): コミットハッシュの基本的な検証を行う。AddResult(): コミットエンティティにビルド結果(Result)を追加する。Datastoreトランザクション内で呼び出されることを想定。HasResult(): 特定のビルダのビルド結果が既に存在するかどうかをチェックする。
Result: 特定のコミット、ビルダ、OS/アーキテクチャの組み合わせに対するビルド結果を表す。Logフィールドがstringから[]byteに変更され、ログデータを直接保持するのではなく、LogHashを介してLogエンティティを参照するようになりました。Key():Packageを親とするキーを生成。Valid(): ハッシュの基本的な検証を行う。
Log: 圧縮されたビルドログを保持するエンティティ。PutLog(): 生のログテキストを受け取り、gzip圧縮してDatastoreに保存し、そのSHA1ハッシュを返す。
Tag:weekly,release,tipなどの特定のタグを表す。Key():Packageを親とするキーを生成。Valid(): タグの種類とハッシュの基本的な検証を行う。
HTTPハンドラの実装
commitHandler(w http.ResponseWriter, r *http.Request):- JSON形式でエンコードされた
Commitオブジェクトをリクエストボディから受け取る。 CommitのValid()メソッドで基本的な検証を行う。- Datastoreトランザクション (
datastore.RunInTransaction) 内でaddCommit関数を呼び出し、コミットをDatastoreに保存する。
- JSON形式でエンコードされた
addCommit(c appengine.Context, com *Commit):- トランザクション内で実行される。
- 既に同じコミットがDatastoreに存在する場合は何もしない。
Packageエンティティから次のコミット番号 (NextNum) を取得し、Commitに割り当てる。- 親コミットが存在するかどうかを検証する(最初のコミットでない場合)。
- Goリポジトリのコミットであれば、
tipタグを更新する。 CommitエンティティをDatastoreに保存する。
tagHandler(w http.ResponseWriter, r *http.Request):- JSON形式でエンコードされた
Tagオブジェクトをリクエストボディから受け取り、Datastoreに保存する。
- JSON形式でエンコードされた
todoHandler(w http.ResponseWriter, r *http.Request):builderとpackagePathクエリパラメータを受け取る。- 指定されたパッケージの最新のコミットから遡り、特定のビルダがまだ結果を報告していない最初のコミットのハッシュを返す。これにより、ビルダは次にどのコミットをビルドすべきかを知ることができる。
resultHandler(w http.ResponseWriter, r *http.Request):- JSON形式でエンコードされた
Resultオブジェクトをリクエストボディから受け取る。 ResultのValid()メソッドで基本的な検証を行う。- ログデータが含まれていれば、
PutLog関数を使ってログを保存し、そのハッシュをResultに設定する。 - Datastoreトランザクション内で
Resultを保存し、関連するCommitエンティティのResultフィールドを更新する。
- JSON形式でエンコードされた
logHandler(w http.ResponseWriter, r *http.Request):- URLパスからログのハッシュを取得し、そのハッシュに対応する圧縮されたログデータをDatastoreから取得する。
- 取得したログデータをgzip展開し、HTTPレスポンスとしてクライアントに返す。
AuthHandler(h http.HandlerFunc) http.HandlerFunc:- HTTPハンドラをラップし、
keyクエリパラメータによる認証を行うミドルウェア関数。 secretKeyとビルダ名から生成されるハッシュ値と、リクエストで提供されたkeyを比較して認証を行う。
- HTTPハンドラをラップし、
init()関数:- アプリケーションの起動時に、各URLパスと対応するハンドラ関数を登録する。
AuthHandlerを使用して認証が必要なエンドポイントを保護している。
- アプリケーションの起動時に、各URLパスと対応するハンドラ関数を登録する。
validHash(hash string) bool:- ハッシュが空でないことを確認するヘルパー関数(TODOコメントでより厳密な検証が必要とされている)。
logErr(w http.ResponseWriter, r *http.Request, err os.Error):- エラーをApp Engineのログに出力し、HTTPレスポンスとしてエラーメッセージを返すヘルパー関数。
misc/dashboard/app/build/key.go の新規追加
このファイルは、認証に使用される secretKey を定義しています。本番環境にデプロイする前に、このキーを秘密の値に設定する必要があることを警告するコメントと、開発サーバでのみ動作するよう panic を含む init 関数が含まれています。
misc/dashboard/app/build/test.go の新規追加
このファイルは、ビルドダッシュボードのバックエンドAPIのテストスイートを含んでいます。
init()関数:/buildtestパスにtestHandlerを登録する。testEntityKinds: テスト中にクリアするDatastoreエンティティの種類を定義。testRequests: 様々なテストケースを定義した構造体のスライス。/commitエンドポイントへのリクエストでコミットを登録。/todoエンドポイントへのリクエストで次にビルドすべきコミットを取得。/resultエンドポイントへのリクエストでビルド結果を登録。/logエンドポイントへのリクエストでログを取得。
testPackages: テスト用のPackageエンティティを定義。testHandler(w http.ResponseWriter, r *http.Request):- 開発サーバでのみ実行されることを確認。
- テスト開始前に
nukeEntitiesを呼び出してDatastoreのテストデータをクリアする。 testPackagesをDatastoreに保存。testRequestsの各テストケースを順に実行し、HTTPリクエストをシミュレートし、レスポンスを検証する。
nukeEntities(c appengine.Context, kinds []string) os.Error:- 指定された種類のDatastoreエンティティを全て削除する関数。テスト環境のクリーンアップに使用される。本番環境での誤操作を防ぐため、開発サーバでのみ実行可能。
コアとなるコードの変更箇所
misc/dashboard/app/app.yaml:/log/.+と/buildtestの新しいURLハンドラが追加されました。
misc/dashboard/app/build/build.go:Package構造体にNextNumフィールドが追加。Commit構造体のNumフィールドの順序が変更され、Keyメソッドが親エンティティ(Package)を持つように変更されました。CommitにValid(),AddResult(),HasResult()メソッドが追加。Result構造体のLogフィールドが[]byteに変更され、Key(),Valid()メソッドが追加。Log構造体とPutLog()関数が追加され、ログの保存と取得ロジックが実装されました。Tag構造体にValid()メソッドが追加。commitHandler,tagHandler,todoHandler,resultHandlerの具体的な実装が追加されました(以前は関数シグネチャのみ)。logHandler,AuthHandler,validHash,logErrヘルパー関数が新規追加。init()関数内で、新しいハンドラの登録とAuthHandlerの適用が行われました。
misc/dashboard/app/build/key.go:secretKey定義を含む新規ファイル。
misc/dashboard/app/build/test.go:- ビルドダッシュボードのAPIをテストするためのテストスイートを含む新規ファイル。
コアとなるコードの解説
このコミットの核心は、Go App Engine上でビルドダッシュボードのバックエンドAPIを構築することにあります。
build.go では、まず Package, Commit, Result, Log, Tag といったGoの構造体がDatastoreのエンティティとして定義されています。これらの構造体には、Datastoreのキーを生成するための Key() メソッドや、データの整合性を検証するための Valid() メソッドが実装されています。特に Commit と Result は、それぞれ Package を親とするエンティティグループ内に配置されることで、関連するデータのトランザクション整合性が保証されます。
HTTPハンドラは、ビルダからのリクエストを処理する主要なコンポーネントです。
commitHandlerは、ビルダが新しいコミット情報をダッシュボードに報告する際に使用されます。コミットはDatastoreに保存され、Goリポジトリの最新コミットであればtipタグも更新されます。resultHandlerは、ビルダがビルド結果を報告する際に使用されます。ビルドログがあれば、それはgzip圧縮されてLogエンティティとして別途保存され、そのハッシュがResultに関連付けられます。その後、ResultはDatastoreに保存され、関連するCommitエンティティも更新されます。todoHandlerは、ビルダが次にどのコミットをビルドすべきかを問い合わせるために使用されます。これは、特定のビルダがまだビルドしていない最新のコミットをDatastoreから検索して返します。logHandlerは、ビルドログのハッシュに基づいて、保存された圧縮ログデータを取得し、展開して提供します。
セキュリティ面では、AuthHandler が重要な役割を果たします。これは、secretKey を用いたシンプルな認証メカニズムを提供し、許可されたビルダのみが commit, result, tag, todo などの機密性の高いAPIエンドポイントにアクセスできるようにします。
key.go は、この認証メカニズムの secretKey を定義するプレースホルダであり、本番デプロイ時には適切な秘密鍵を設定する必要があることを開発者に促しています。
test.go は、これらのバックエンドAPIが正しく機能するかを検証するための包括的なテストスイートを提供します。nukeEntities 関数を使ってテスト前にDatastoreをクリーンアップし、様々なリクエストをシミュレートしてAPIの動作とレスポンスを検証することで、実装の信頼性を確保しています。
全体として、このコミットは、Goプロジェクトのビルドダッシュボードがビルダからの情報を効率的かつ安全に収集・管理するための堅牢なバックエンドシステムを構築しています。
関連リンク
参考にした情報源リンク
- コミットメッセージと変更されたソースコード
- Google App Engineのドキュメント(Go言語、Datastoreに関する一般的な知識)
- Go言語の標準ライブラリ(
net/http,encoding/json,compress/gzip,crypto/sha1など)の一般的な知識 - 継続的インテグレーションとビルドダッシュボードに関する一般的な概念