Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 11629] ファイルの概要

このコミットは、Go言語のダッシュボードアプリケーションを go1beta リリースに合わせて更新するための変更を含んでいます。主な変更点は、Go標準ライブラリのAPI変更への対応であり、具体的にはエラーハンドリングの os.Error から組み込みの error インターフェースへの移行、パッケージパスの変更(例: http から net/http)、および時間型 (datastore.Time から time.Time) の更新が含まれます。これにより、ダッシュボードが新しいGoのバージョンで正しく動作するようになります。

コミット

commit 22185aed742bba5e85b1e4bac68e4c50be84a227
Author: Andrew Gerrand <adg@golang.org>
Date:   Mon Feb 6 09:26:32 2012 +1100

    dashboard: update to go1beta
    
    R=golang-dev, dsymonds
    CC=golang-dev
    https://golang.org/cl/5624056

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/22185aed742bba5e85b1e4bac68e4c50be84a227

元コミット内容

dashboard: update to go1beta

R=golang-dev, dsymonds
CC=golang-dev
https://golang.org/cl/5624056

変更の背景

このコミットの背景には、Go言語のバージョンアップ、特に go1beta リリースに向けた標準ライブラリのAPI変更があります。Go言語は、バージョン1.0の安定版リリースに向けて、APIの安定化と整理を進めていました。その過程で、いくつかの重要な変更が導入されました。

主な変更点は以下の通りです。

  1. os.Error から error インターフェースへの移行: Go 1.0より前のバージョンでは、エラーを表現するために os.Error 型が広く使われていました。しかし、Go 1.0では、より柔軟で標準的なエラーハンドリングを可能にするために、組み込みの error インターフェースが導入され、os.Error は非推奨となりました。この変更により、エラーを返す関数は os.Error の代わりに error インターフェースを返すように修正する必要がありました。
  2. 標準ライブラリのパッケージパスの整理: http パッケージが net/http に、template パッケージが html/templatetext/template に分割・移動されるなど、標準ライブラリのパッケージ構造が整理されました。これにより、コード内でこれらのパッケージをインポートしている箇所を新しいパスに更新する必要が生じました。
  3. time パッケージの変更: 時間の扱いに関するAPIも一部変更され、datastore.Time のような特定の型から、より汎用的な time.Time 型への移行が必要となりました。また、time.Seconds() のような関数が非推奨となり、time.Now().Unix()time.Now().Add() のような新しいAPIが推奨されるようになりました。
  4. hmac パッケージのAPI変更: hmac.NewMD5 のような特定のハッシュ関数に特化したコンストラクタが非推奨となり、より汎用的な hmac.New(hash.Hash, key []byte) の形式に統一されました。

これらの変更は、Go言語のAPIをより一貫性があり、使いやすく、将来にわたって安定したものにするための重要なステップでした。GoダッシュボードはGo言語のビルドやテストの状態を監視する重要なツールであるため、これらのAPI変更に追従し、go1beta 環境で正しく動作するように更新する必要がありました。

前提知識の解説

Go言語のエラーハンドリング (os.Error から error へ)

Go言語では、エラーは戻り値として扱われることが一般的です。Go 1.0より前のバージョンでは、os パッケージに定義された os.Error インターフェースがエラーを表すために使用されていました。これは、以下のように定義されていました。

package os

type Error interface {
    String() string
}

しかし、Go 1.0からは、より汎用的な組み込みの error インターフェースが導入されました。

package builtin

type error interface {
    Error() string
}

この変更の主な理由は、os.Erroros パッケージに依存していたため、エラーの概念がファイルシステム操作に限定されるような印象を与えていたことです。新しい error インターフェースは、Go言語全体でエラーを表現するための標準的な方法となり、任意の型が Error() string メソッドを実装することでエラーとして扱えるようになりました。これにより、エラーの表現がより柔軟になり、カスタムエラー型の作成が容易になりました。

このコミットでは、関数シグネチャの os.Errorerror に変更し、os.NewError の呼び出しを errors.New に置き換えることで、この新しいエラーハンドリングの慣習に準拠しています。

Go標準ライブラリのパッケージ構造の変更

Go言語の初期開発段階では、標準ライブラリのパッケージ構造が頻繁に調整されていました。特に、Go 1.0の安定化プロセスにおいて、パッケージの役割と責任を明確にするための再編成が行われました。

  • http から net/http: ネットワークプロトコルに関連する機能は、より一般的な net パッケージの下に統合されました。これにより、HTTPクライアントおよびサーバー機能は net/http パッケージに移動しました。
  • template から html/template および text/template: テンプレートエンジンは、その用途に応じてHTMLエスケープを自動的に行う html/template と、プレーンテキストを生成する text/template に分割されました。これにより、セキュリティ(XSS攻撃の防止など)と柔軟性の両方が向上しました。

これらの変更は、Goの標準ライブラリが大規模なアプリケーション開発においてより堅牢で使いやすくなることを目指したものでした。

Google App Engine Go SDK と datastore.Time

Google App Engine (GAE) は、Googleのインフラストラクチャ上でウェブアプリケーションを構築・ホストするためのプラットフォームです。Go言語はGAEでサポートされており、GAEのデータストア(NoSQLデータベース)と連携するためのSDKが提供されています。

Go 1.0より前のGAE Go SDKでは、データストアに時間を保存するために datastore.Time というカスタム型が使用されていました。しかし、Go 1.0のリリースに伴い、Go標準ライブラリの time.Time 型がより成熟し、データストアとの連携も time.Time を直接使用するように変更されました。これにより、GAEアプリケーションはGoの標準的な時間型をそのまま利用できるようになり、コードの移植性と一貫性が向上しました。

hmac パッケージのAPI変更

crypto/hmac パッケージは、HMAC (Keyed-Hash Message Authentication Code) を実装するための機能を提供します。Goの初期バージョンでは、特定のハッシュ関数(例: MD5)に特化した hmac.NewMD5 のようなコンストラクタが存在しました。しかし、より汎用的な設計として、hmac.New 関数が導入され、任意の hash.Hash インターフェースを実装するハッシュ関数を受け入れるようになりました。これにより、HMACの計算に使用するハッシュ関数を柔軟に選択できるようになりました。

このコミットでは、hmac.NewMD5([]byte(secretKey(c)))hmac.New(md5.New, []byte(secretKey(c))) に変更されており、このAPIの変更に対応しています。

技術的詳細

このコミットで行われた技術的な変更は、主にGo言語の go1beta リリースに伴うAPIの非互換性に対応するためのものです。

  1. エラー型の変更:

    • os.Error 型が使用されていたすべての関数シグネチャが error 型に変更されました。
    • os.NewError("...") の呼び出しは errors.New("...") に置き換えられました。これには、build/build.gobuild/handler.gobuild/test.gobuild/ui.gobuild/key.go など、多くのファイルが影響を受けています。
    • err.String() メソッドの呼び出しは err.Error() に変更されました。これは error インターフェースのメソッド名に合わせたものです。
  2. パッケージインポートパスの変更:

    • "http" パッケージのインポートは "net/http" に変更されました。これは build/handler.gobuild/init.gobuild/test.gobuild/ui.gocache/cache.go など、HTTP関連の処理を行うすべてのファイルに適用されています。
    • "json" パッケージのインポートは "encoding/json" に変更されました。これは build/handler.gobuild/test.go に影響しています。
    • "template" パッケージのインポートは "text/template" または "html/template" に変更されました。
      • build/notify.go では "template""text/template" に変更され、template.Must の呼び出しも template.New("notify.txt").Funcs(template.FuncMap(tmplFuncs)).ParseFiles("build/notify.txt") のように、より明示的な ParseFiles を使用するように変更されています。
      • build/ui.go では "exp/template/html""template" が削除され、"html/template" が追加されています。これはHTMLテンプレートの処理に特化したパッケージを使用するためです。
  3. 時間型の変更:

    • Commit 構造体の Time フィールドの型が datastore.Time から time.Time に変更されました。
    • time.Seconds() の使用箇所が time.Now().Unix() に変更され、datastore.Time(tCommitTime * 1e6) のようなデータストア固有の時間変換が不要になりました。
    • テストコード (build/test.go) では、tCommitTime の初期化が time.Seconds() - 60*60*24*7 から time.Now().Add(-time.Hour * 24 * 7) に変更され、tCommitTime += 60 * 60 * 12tCommitTime.Add(time.Hour) に変更されています。これは time.Time 型のメソッドを利用したよりGoらしい時間の操作です。
  4. HMACハッシュ関数の指定方法の変更:

    • build/handler.gobuilderKey 関数内で、hmac.NewMD5([]byte(secretKey(c)))hmac.New(md5.New, []byte(secretKey(c))) に変更されました。これは、hmac.New 関数が hash.Hash インターフェースを実装する任意のハッシュ関数を受け入れるようになったためです。
  5. sha1.Sum() の引数変更:

    • build/build.goPutLog 関数内で、h.Sum()h.Sum(nil) に変更されました。Sum メソッドは、Go 1.0で引数としてバイトスライスを受け取るようになり、そのスライスにハッシュ値を追記するようになりました。nil を渡すことで、新しいスライスが割り当てられ、ハッシュ値が返されます。

これらの変更は、Go言語のAPIの進化と安定化の過程を反映しており、Go 1.0以降のバージョンでコードが正しく動作するための必須の更新でした。

コアとなるコードの変更箇所

このコミットにおけるコアとなるコードの変更箇所は、主に以下のファイルと行に集中しています。

  1. misc/dashboard/app/app.yaml:

    --- a/misc/dashboard/app/app.yaml
    +++ b/misc/dashboard/app/app.yaml
    @@ -6,7 +6,7 @@
     application: golang-org
     version: build
     runtime: go
    -api_version: 3
    +api_version: go1beta
     
     handlers:
     - url: /static
    
    • App EngineアプリケーションのAPIバージョンを 3 から go1beta に更新。
  2. misc/dashboard/app/build/build.go:

    --- a/misc/dashboard/app/build/build.go
    +++ b/misc/dashboard/app/build/build.go
    @@ -8,11 +8,12 @@ import (
     	"bytes"
     	"compress/gzip"
     	"crypto/sha1"
    +	"errors"
     	"fmt"
     	"io"
     	"io/ioutil"
    -	"os"
     	"strings"
    +	"time"
     
     	"appengine"
     	"appengine/datastore"
    @@ -41,7 +42,7 @@ func (p *Package) Key(c appengine.Context) *datastore.Key {
     }
     
     // LastCommit returns the most recent Commit for this Package.
    -func (p *Package) LastCommit(c appengine.Context) (*Commit, os.Error) {
    +func (p *Package) LastCommit(c appengine.Context) (*Commit, error) {
     	var commits []*Commit
     	_, err := datastore.NewQuery("Commit").
     		Ancestor(p.Key(c)).
    @@ -58,7 +59,7 @@ func (p *Package) LastCommit(c appengine.Context) (*Commit, os.Error) {
     }
     
     // GetPackage fetches a Package by path from the datastore.
    -func GetPackage(c appengine.Context, path string) (*Package, os.Error) {
    +func GetPackage(c appengine.Context, path string) (*Package, error) {
     	p := &Package{Path: path}
     	err := datastore.Get(c, p.Key(c), p)
     	if err == datastore.ErrNoSuchEntity {
    @@ -80,7 +81,7 @@ type Commit struct {
     
     	User string
     	Desc string `datastore:",noindex"`
    -	Time datastore.Time
    +	Time time.Time
     
     	// ResultData is the Data string of each build Result for this Commit.
     	// For non-Go commits, only the Results for the current Go tip, weekly,
    @@ -100,19 +101,19 @@ func (com *Commit) Key(c appengine.Context) *datastore.Key {
     	return datastore.NewKey(c, "Commit", key, 0, p.Key(c))
     }
     
    -func (c *Commit) Valid() os.Error {
    +func (c *Commit) Valid() error {
     	if !validHash(c.Hash) {
    -		return os.NewError("invalid Hash")
    +		return errors.New("invalid Hash")
     	}
     	if c.ParentHash != "" && !validHash(c.ParentHash) { // empty is OK
    -		return os.NewError("invalid ParentHash")
    +		return errors.New("invalid ParentHash")
     	}
     	return nil
     }
     
     // AddResult adds the denormalized Reuslt data to the Commit's Result field.
     // It must be called from inside a datastore transaction.
    -func (com *Commit) AddResult(c appengine.Context, r *Result) os.Error {
    +func (com *Commit) AddResult(c appengine.Context, r *Result) error {
     	if err := datastore.Get(c, com.Key(c), com); err != nil {
     		return fmt.Errorf("getting Commit: %v", err)
     	}
    @@ -192,12 +193,12 @@ func (r *Result) Key(c appengine.Context) *datastore.Key {
     	return datastore.NewKey(c, "Result", key, 0, p.Key(c))
     }
     
    -func (r *Result) Valid() os.Error {
    +func (r *Result) Valid() error {
     	if !validHash(r.Hash) {
    -		return os.NewError("invalid Hash")
    +		return errors.New("invalid Hash")
     	}
     	if r.PackagePath != "" && !validHash(r.GoHash) {
    -		return os.NewError("invalid GoHash")
    +		return errors.New("invalid GoHash")
     	}
     	return nil
     }
    @@ -214,7 +215,7 @@ type Log struct {
     	CompressedLog []byte
     }
     
    -func (l *Log) Text() ([]byte, os.Error) {
    +func (l *Log) Text() ([]byte, error) {
     	d, err := gzip.NewReader(bytes.NewBuffer(l.CompressedLog))
     	if err != nil {
     		return nil, fmt.Errorf("reading log data: %v", err)
    @@ -226,14 +227,14 @@ func (l *Log) Text() ([]byte, os.Error) {
     	return b, nil
     }
     
    -func PutLog(c appengine.Context, text string) (hash string, err os.Error) {
    +func PutLog(c appengine.Context, text string) (hash string, err error) {
     	h := sha1.New()
     	io.WriteString(h, text)
     	b := new(bytes.Buffer)
     	z, _ := gzip.NewWriterLevel(b, gzip.BestCompression)
     	io.WriteString(z, text)
     	z.Close()
    -	hash = fmt.Sprintf("%x", h.Sum())
    +	hash = fmt.Sprintf("%x", h.Sum(nil))
     	key := datastore.NewKey(c, "Log", hash, 0, nil)
     	_, err = datastore.Put(c, key, &Log{b.Bytes()})
     	return
    @@ -252,29 +253,29 @@ func (t *Tag) Key(c appengine.Context) *datastore.Key {
     	return datastore.NewKey(c, "Tag", t.Kind, 0, p.Key(c))
     }
     
    -func (t *Tag) Valid() os.Error {
    +func (t *Tag) Valid() error {
     	if t.Kind != "weekly" && t.Kind != "release" && t.Kind != "tip" {
    -		return os.NewError("invalid Kind")
    +		return errors.New("invalid Kind")
     	}
     	if !validHash(t.Hash) {
    -		return os.NewError("invalid Hash")
    +		return errors.New("invalid Hash")
     	}
     	return nil
     }
     
     // Commit returns the Commit that corresponds with this Tag.
    -func (t *Tag) Commit(c appengine.Context) (*Commit, os.Error) {
    +func (t *Tag) Commit(c appengine.Context) (*Commit, error) {
     	com := &Commit{Hash: t.Hash}
     	err := datastore.Get(c, com.Key(c), com)
     	return com, err
     }
     
     // GetTag fetches a Tag by name from the datastore.
    -func GetTag(c appengine.Context, tag string) (*Tag, os.Error) {
    +func GetTag(c appengine.Context, tag string) (*Tag, error) {
     	t := &Tag{Kind: tag}
     	if err := datastore.Get(c, t.Key(c), t); err != nil {
     		if err == datastore.ErrNoSuchEntity {
    -			return nil, os.NewError("tag not found: " + tag)
    +			return nil, errors.New("tag not found: " + tag)
     		}
     		return nil, err
     	}
    @@ -286,11 +287,11 @@ func GetTag(c appengine.Context, tag string) (*Tag, os.Error) {
     
     // Packages returns packages of the specified kind.
     // Kind must be one of "external" or "subrepo".
    -func Packages(c appengine.Context, kind string) ([]*Package, os.Error) {
    +func Packages(c appengine.Context, kind string) ([]*Package, error) {
     	switch kind {
     	case "external", "subrepo":
     	default:
    -		return nil, os.NewError(`kind must be one of "external" or "subrepo"`)
    +		return nil, errors.New(`kind must be one of "external" or "subrepo"`)
     	}
     	var pkgs []*Package
     	q := datastore.NewQuery("Package").Filter("Kind=", kind)
    
    • os インポートの削除と errors, time の追加。
    • os.Errorerror に、os.NewErrorerrors.New に変更。
    • Commit 構造体の Time フィールドを datastore.Time から time.Time に変更。
    • sha1.Sum() の呼び出しを h.Sum() から h.Sum(nil) に変更。
  3. misc/dashboard/app/build/handler.go:

    --- a/misc/dashboard/app/build/handler.go
    +++ b/misc/dashboard/app/build/handler.go
    @@ -6,10 +6,11 @@ package build
     
     import (
     	"crypto/hmac"
    +	"crypto/md5"
    +	"encoding/json"
    +	"errors"
     	"fmt"
    -	"http"
    -	"json"
    -	"os"
    +	"net/http"
     
     	"appengine"
     	"appengine/datastore"
    @@ -28,7 +29,7 @@ const commitsPerPage = 30
     // each new commit at tip.
     //
     // This handler is used by a gobuilder process in -commit mode.
    -func commitHandler(r *http.Request) (interface{}, os.Error) {
    +func commitHandler(r *http.Request) (interface{}, error) {
     	c := appengine.NewContext(r)
     	com := new(Commit)
     
    @@ -56,7 +57,7 @@ func commitHandler(r *http.Request) (interface{}, os.Error) {
     	\treturn nil, fmt.Errorf("validating Commit: %v", err)
     	}
     	defer cache.Tick(c)
    -	tx := func(c appengine.Context) os.Error {
    +	tx := func(c appengine.Context) error {
     		return addCommit(c, com)
     	}
     	return nil, datastore.RunInTransaction(c, tx, nil)
    @@ -64,7 +65,7 @@ func commitHandler(r *http.Request) (interface{}, os.Error) {
     
     // addCommit adds the Commit entity to the datastore and updates the tip Tag.
     // It must be run inside a datastore transaction.
    -func addCommit(c appengine.Context, com *Commit) os.Error {
    +func addCommit(c appengine.Context, com *Commit) error {
     	var tc Commit // temp value so we don't clobber com
     	err := datastore.Get(c, com.Key(c), &tc)
     	if err != datastore.ErrNoSuchEntity {
    @@ -94,7 +95,7 @@ func addCommit(c appengine.Context, com *Commit) os.Error {
     		\treturn fmt.Errorf("testing for parent Commit: %v", err)
     		}
     		if n == 0 {
    -			return os.NewError("parent commit not found")
    +			return errors.New("parent commit not found")
     		}
     	}
     	// update the tip Tag if this is the Go repo
    @@ -115,7 +116,7 @@ func addCommit(c appengine.Context, com *Commit) os.Error {
     // request body and updates the Tag entity for the Kind of tag provided.
     //
     // This handler is used by a gobuilder process in -commit mode.
    -func tagHandler(r *http.Request) (interface{}, os.Error) {
    +func tagHandler(r *http.Request) (interface{}, error) {
     	if r.Method != "POST" {
     		return nil, errBadMethod(r.Method)
     	}
    @@ -143,7 +144,7 @@ type Todo struct {
     // todoHandler returns the next action to be performed by a builder.
     // It expects "builder" and "kind" query parameters and returns a *Todo value.
     // Multiple "kind" parameters may be specified.
    -func todoHandler(r *http.Request) (interface{}, os.Error) {
    +func todoHandler(r *http.Request) (interface{}, error) {
     	c := appengine.NewContext(r)
     	now := cache.Now(c)
     	key := "build-todo-" + r.Form.Encode()
    @@ -151,7 +152,7 @@ func todoHandler(r *http.Request) (interface{}, os.Error) {
     	if cache.Get(r, now, key, &todo) {
     		return todo, nil
     	}
    -	var err os.Error
    +	var err error
     	builder := r.FormValue("builder")
     	for _, kind := range r.Form["kind"] {
     		var data interface{}
    @@ -183,7 +184,7 @@ func todoHandler(r *http.Request) (interface{}, os.Error) {
     // If provided with non-empty packagePath and goHash args, it scans the first
     // 20 Commits in Num-descending order for the specified packagePath and
     // returns the first that doesn't have a Result for this builder and goHash.
    -func buildTodo(c appengine.Context, builder, packagePath, goHash string) (interface{}, os.Error) {
    +func buildTodo(c appengine.Context, builder, packagePath, goHash string) (interface{}, error) {
     	p, err := GetPackage(c, packagePath)
     	if err != nil {
     		return nil, err
    @@ -251,7 +252,7 @@ func buildTodo(c appengine.Context, builder, packagePath, goHash string) (interf
     
     // packagesHandler returns a list of the non-Go Packages monitored
     // by the dashboard.
    -func packagesHandler(r *http.Request) (interface{}, os.Error) {
    +func packagesHandler(r *http.Request) (interface{}, error) {
     	kind := r.FormValue("kind")
     	c := appengine.NewContext(r)
     	now := cache.Now(c)
    @@ -273,7 +274,7 @@ func packagesHandler(r *http.Request) (interface{}, os.Error) {
     // creates a new Result entity, and updates the relevant Commit entity.
     // If the Log field is not empty, resultHandler creates a new Log entity
     // and updates the LogHash field before putting the Commit entity.
    -func resultHandler(r *http.Request) (interface{}, os.Error) {
    +func resultHandler(r *http.Request) (interface{}, error) {
     	if r.Method != "POST" {
     		return nil, errBadMethod(r.Method)
     	}
    @@ -296,7 +297,7 @@ func resultHandler(r *http.Request) (interface{}, os.Error) {
     		}
     		res.LogHash = hash
     	}
    -	tx := func(c appengine.Context) os.Error {
    +	tx := func(c appengine.Context) error {
     		// check Package exists
     		if _, err := GetPackage(c, res.PackagePath); err != nil {
     			return fmt.Errorf("GetPackage: %v", err)
    @@ -338,7 +339,7 @@ func logHandler(w http.ResponseWriter, r *http.Request) {
     	w.Write(b)
     }
     
    -type dashHandler func(*http.Request) (interface{}, os.Error)
    +type dashHandler func(*http.Request) (interface{}, error)
     
     type dashResponse struct {
     	Response interface{}
    @@ -349,7 +350,7 @@ type dashResponse struct {
     // the request has an unsuitable method.
     type errBadMethod string
     
    -func (e errBadMethod) String() string {
    +func (e errBadMethod) Error() string {
     	return "bad method: " + string(e)
     }
     
    @@ -363,14 +364,14 @@ func AuthHandler(h dashHandler) http.HandlerFunc {
     		// request body when calling r.FormValue.
     		r.Form = r.URL.Query()
     
    -		var err os.Error
    +		var err error
     		var resp interface{}
     
     		// Validate key query parameter for POST requests only.
     		key := r.FormValue("key")
     		builder := r.FormValue("builder")
     		if r.Method == "POST" && !validKey(c, key, builder) {
    -			err = os.NewError("invalid key: " + key)
    +			err = errors.New("invalid key: " + key)
     		}
     
     		// Call the original HandlerFunc and return the response.
    @@ -382,7 +383,7 @@ func AuthHandler(h dashHandler) http.HandlerFunc {
     		dashResp := &dashResponse{Response: resp}
     		if err != nil {
     			c.Errorf("%v", err)
    -			dashResp.Error = err.String()
    +			dashResp.Error = err.Error()
     		}
     		w.Header().Set("Content-Type", "application/json")
     		if err = json.NewEncoder(w).Encode(dashResp); err != nil {
    @@ -394,7 +395,7 @@ func keyHandler(w http.ResponseWriter, r *http.Request) {
     func keyHandler(w http.ResponseWriter, r *http.Request) {
     	builder := r.FormValue("builder")
     	if builder == "" {
    -		logErr(w, r, os.NewError("must supply builder in query string"))
    +		logErr(w, r, errors.New("must supply builder in query string"))
     		return
     	}
     	c := appengine.NewContext(r)
    @@ -433,12 +434,12 @@ func validKey(c appengine.Context, key, builder string) bool {
     }
     
     func builderKey(c appengine.Context, builder string) string {
    -	h := hmac.NewMD5([]byte(secretKey(c)))
    +	h := hmac.New(md5.New, []byte(secretKey(c)))
     	h.Write([]byte(builder))
    -	return fmt.Sprintf("%x", h.Sum())
    +	return fmt.Sprintf("%x", h.Sum(nil))
     }
     
    -func logErr(w http.ResponseWriter, r *http.Request, err os.Error) {
    +func logErr(w http.ResponseWriter, r *http.Request, err error) {
     	appengine.NewContext(r).Errorf("Error: %v", err)
     	w.WriteHeader(http.StatusInternalServerError)
     	fmt.Fprint(w, "Error: ", err)
    
    • http, json, os インポートの削除と crypto/md5, encoding/json, errors, net/http の追加。
    • os.Errorerror に、os.NewErrorerrors.New に変更。
    • errBadMethod 型の String() メソッドを Error() メソッドに変更。
    • hmac.NewMD5hmac.New(md5.New, ...) に変更。
    • h.Sum()h.Sum(nil) に変更。
  4. misc/dashboard/app/build/test.go:

    --- a/misc/dashboard/app/build/test.go
    +++ b/misc/dashboard/app/build/test.go
    @@ -10,15 +10,15 @@ import (
     	"appengine"
     	"appengine/datastore"
     	"bytes"
    +	"encoding/json"
    +	"errors"
     	"fmt"
    -	"http"
    -	"http/httptest"
     	"io"
    -	"json"
    -	"os"
    +	"net/http"
    +	"net/http/httptest"
    +	"net/url"
     	"strings"
     	"time"
    -	"url"
     )
     
     func init() {
    @@ -41,14 +41,14 @@ var testPackages = []*Package{
     	testPackage,
     }
     
    -var tCommitTime = time.Seconds() - 60*60*24*7
    +var tCommitTime = time.Now().Add(-time.Hour * 24 * 7)
     
     func tCommit(hash, parentHash string) *Commit {
    -	tCommitTime += 60 * 60 * 12 // each commit should have a different time
    +	tCommitTime.Add(time.Hour) // each commit should have a different time
     	return &Commit{
     		Hash:       hash,
     		ParentHash: parentHash,
    -		Time:       datastore.Time(tCommitTime * 1e6),
    +		Time:       tCommitTime,
     		User:       "adg",
     		Desc:       "change description",
     	}
    @@ -233,9 +233,9 @@ func testHandler(w http.ResponseWriter, r *http.Request) {
     	fmt.Fprint(w, "PASS")
     }
     
    -func nukeEntities(c appengine.Context, kinds []string) os.Error {
    +func nukeEntities(c appengine.Context, kinds []string) error {
     	if !appengine.IsDevAppServer() {
    -		return os.NewError("can't nuke production data")
    +		return errors.New("can't nuke production data")
     	}
     	var keys []*datastore.Key
     	for _, kind := range kinds {
    
    • http, http/httptest, json, os, url インポートの削除と encoding/json, errors, net/http, net/http/httptest, net/url の追加。
    • tCommitTime の初期化と更新方法を time.Time 型のメソッドを使用するように変更。
    • os.Errorerror に、os.NewErrorerrors.New に変更。
  5. misc/dashboard/app/build/ui.go:

    --- a/misc/dashboard/app/build/ui.go
    +++ b/misc/dashboard/app/build/ui.go
    @@ -9,14 +9,13 @@ package build
     
     import (
     	"bytes"
    -	"exp/template/html"
    -	"http"
    -	"os"
    +	"errors"
    +	"html/template"
    +	"net/http"
     	"regexp"
     	"sort"
     	"strconv"
     	"strings"
    -	"template"
     
     	"appengine"
     	"appengine/datastore"
    @@ -25,7 +24,6 @@ import (
     
     func init() {
      	http.HandleFunc("/", uiHandler)
    -	html.Escape(uiTemplate)
     }
     
     // uiHandler draws the build status page.
    @@ -96,7 +94,7 @@ type Pagination struct {
     
     // goCommits gets a slice of the latest Commits to the Go repository.
     // If page > 0 it paginates by commitsPerPage.
    -func goCommits(c appengine.Context, page int) ([]*Commit, os.Error) {
    +func goCommits(c appengine.Context, page int) ([]*Commit, error) {
     	q := datastore.NewQuery("Commit").
     		Ancestor((&Package{}).Key(c)).
     		Order("-Time").
    @@ -140,7 +138,7 @@ type PackageState struct {\n }
     
     // TagStateByName fetches the results for all Go subrepos at the specified Tag.
    -func TagStateByName(c appengine.Context, name string) (*TagState, os.Error) {
    +func TagStateByName(c appengine.Context, name string) (*TagState, error) {
     	tag, err := GetTag(c, name)
     	if err != nil {
     		return nil, err
    @@ -173,7 +171,7 @@ type uiTemplateData struct {
     }
     
     var uiTemplate = template.Must(
    -	template.New("ui").Funcs(tmplFuncs).ParseFile("build/ui.html"),
    +	template.New("ui.html").Funcs(tmplFuncs).ParseFiles("build/ui.html"),
     )
     
     var tmplFuncs = template.FuncMap{
    @@ -293,13 +291,13 @@ func shortUser(user string) string {
     var repoRe = regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\\-]+)(\\.[a-z0-9\\-]+)?$`)
     
     // repoURL returns the URL of a change at a Google Code repository or subrepo.
    -func repoURL(hash, packagePath string) (string, os.Error) {
    +func repoURL(hash, packagePath string) (string, error) {
     	if packagePath == "" {
     		return "https://code.google.com/p/go/source/detail?r=" + hash, nil
     	}
     	m := repoRe.FindStringSubmatch(packagePath)
     	if m == nil {
    -		return "", os.NewError("unrecognized package: " + packagePath)
    +		return "", errors.New("unrecognized package: " + packagePath)
     	}
     	url := "https://code.google.com/p/" + m[1] + "/source/detail?r=" + hash
     	if len(m) > 2 {
    
    • exp/template/html, http, os, template インポートの削除と errors, html/template, net/http の追加。
    • html.Escape(uiTemplate) の削除。
    • os.Errorerror に、os.NewErrorerrors.New に変更。
    • template.Must の呼び出しを template.New("ui.html").Funcs(tmplFuncs).ParseFiles("build/ui.html") に変更。

コアとなるコードの解説

これらの変更は、Go言語の go1beta リリースで導入されたAPIの変更に、Goダッシュボードアプリケーションを適応させるためのものです。

  • app.yamlapi_version: go1beta: これは、Google App Engine上でGoアプリケーションを実行する際に、使用するGoのAPIバージョンを指定するものです。go1beta を指定することで、アプリケーションがGo 1.0のベータ版APIを使用することをApp Engineに伝えます。これにより、新しいAPIの動作が保証されます。

  • os.Error から error への移行:

    • Go 1.0では、エラーハンドリングの標準として組み込みの error インターフェースが採用されました。これにより、os.Error を使用していたすべての関数シグネチャが error に変更され、エラーを生成する際には errors パッケージの New 関数が使用されるようになりました。
    • 例: func (p *Package) LastCommit(c appengine.Context) (*Commit, os.Error)func (p *Package) LastCommit(c appengine.Context) (*Commit, error) に変更され、return os.NewError("invalid Hash")return errors.New("invalid Hash") に変更されています。
    • err.String()err.Error() に変更されたのも、error インターフェースのメソッド名に合わせたものです。
  • パッケージインポートパスの変更:

    • http から net/httpjson から encoding/jsontemplate から text/template または html/template への変更は、Go標準ライブラリのパッケージ構造の整理によるものです。これにより、より論理的で一貫性のあるパッケージ構成が実現され、開発者は目的の機能を見つけやすくなりました。
    • 特に html/template の導入は、ウェブアプリケーションにおけるXSS攻撃を防ぐための自動エスケープ機能を提供し、セキュリティを向上させます。html.Escape(uiTemplate) の削除は、html/template が自動的にエスケープ処理を行うため、明示的なエスケープが不要になったことを示しています。
  • datastore.Time から time.Time への移行:

    • Commit 構造体内の Time フィールドが datastore.Time からGo標準ライブラリの time.Time に変更されたことで、データストアとの連携がよりシームレスになり、Goの標準的な時間操作関数を直接利用できるようになりました。
    • テストコードにおける tCommitTime の初期化と更新方法の変更は、time.Time 型のメソッド (time.Now().Add()) を使用することで、より自然で読みやすい時間の操作が可能になったことを示しています。
  • hmac.NewMD5 から hmac.New(md5.New, ...) への変更:

    • これは crypto/hmac パッケージのAPIがより汎用的に設計された結果です。以前は特定のハッシュ関数に特化したコンストラクタがありましたが、新しいAPIでは hmac.New 関数に任意の hash.Hash インターフェースを実装するハッシュ関数(例: md5.New())を渡すことで、柔軟にHMACを生成できるようになりました。
  • sha1.Sum() から sha1.Sum(nil) への変更:

    • crypto/sha1 パッケージの Sum メソッドのシグネチャ変更に対応したものです。Go 1.0以降、Sum メソッドはバイトスライスを引数として受け取り、そのスライスにハッシュ値を追記するようになりました。nil を渡すことで、新しいスライスが割り当てられ、ハッシュ値が返されます。

これらの変更は、GoダッシュボードがGo言語の進化に追従し、最新のGo環境で安定して動作するための重要なステップでした。

関連リンク

参考にした情報源リンク