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

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

このコミットは、Go言語のリリースプロセスで使用される misc/makerelease ツールに対する変更です。具体的には、新しいダウンロードページへのアップロード報告メカニズムを導入し、リリースされたバイナリやソースアーカイブの情報を適切に新しいシステムに送信するように更新しています。これにより、Goの公式ダウンロードページがより正確かつ効率的に更新されるようになります。

コミット

commit 865904f6d8c1dd80b1a203c531d0eb03e7f403f7
Author: Andrew Gerrand <adg@golang.org>
Date:   Mon Jun 2 12:46:03 2014 +1000

    misc/makerelease: report uploads to the new downloads page
    
    LGTM=bradfitz
    R=golang-codereviews, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/102040047

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

https://github.com/golang/go/commit/865904f6d8c1dd80b1a203c531d0eb03e7f403f7

元コミット内容

このコミットは、misc/makerelease ツールが、Goの新しいダウンロードページにアップロード情報を報告するように変更するものです。以前は go.googlecode.com/files に直接ファイルをアップロードしていましたが、新しいダウンロードページでは、アップロードされたファイルに関するメタデータ(ファイル名、バージョン、OS、アーキテクチャ、チェックサム、種類など)をHTTP POSTリクエストで特定のURL (golang.org/dl/upload) に送信することで、ダウンロードページが動的に更新される仕組みに移行しました。

変更の背景

Go言語のリリースプロセスは、新しいバージョンのGoが公開されるたびに、そのバイナリやソースコードをユーザーがダウンロードできるようにするための重要な手順です。以前は、Google Codeのファイルホスティングサービス(go.googlecode.com/files)を利用してリリースファイルを公開していました。しかし、このシステムは静的なファイルホスティングであり、ダウンロードページの情報を手動で更新する必要があるか、あるいは限定的な自動化しか提供していなかった可能性があります。

このコミットが行われた2014年頃は、Google Codeが徐々にその役割を終え、GitHubなどの他のプラットフォームへの移行が進んでいた時期と重なります。Goプロジェクトも、より柔軟で動的なダウンロードページを提供するために、インフラストラクチャの変更を計画していました。

この変更の主な背景は以下の通りです。

  1. ダウンロードページの刷新: ユーザーエクスペリエンスの向上と、より詳細な情報(チェックサム、ファイルの種類など)を提供できる新しいダウンロードページへの移行。
  2. 自動化の強化: リリースファイルのアップロードと同時に、ダウンロードページの情報も自動的に更新されるようにすることで、手動での作業を減らし、エラーのリスクを低減する。
  3. Google Codeからの移行: Google Codeのサービス終了を見据え、Goのリリースインフラをより持続可能なものにする必要があった。新しいシステムでは、Google Cloud Storageにファイルを保存し、そのメタデータを新しいダウンロードページに報告する形に変わりました。

これにより、Goのリリース管理がより堅牢で自動化されたものになり、ユーザーは常に最新かつ正確なダウンロード情報にアクセスできるようになりました。

前提知識の解説

このコミットを理解するためには、以下の技術的背景知識が役立ちます。

  • Go言語のリリースプロセス: Go言語の新しいバージョンがリリースされる際の一連の作業。これには、ソースコードのビルド、各種プラットフォーム向けバイナリの生成、テスト、そして生成されたファイルの公開が含まれます。misc/makerelease はこのプロセスの一部を自動化するツールです。
  • misc/makerelease ツール: Goプロジェクトの misc ディレクトリにある内部ツールで、Goの公式リリースをビルドし、公開するためのスクリプトやユーティリティが含まれています。このツールは、Goのソースからコンパイラやツールチェインをビルドし、様々なOS/アーキテクチャ向けのバイナリパッケージを作成し、それらをストレージにアップロードする役割を担っています。
  • Google Cloud Storage (GCS): Googleが提供するオブジェクトストレージサービス。大量のデータを保存し、世界中からアクセス可能にするためのスケーラブルなインフラを提供します。Goのリリースファイルは、このGCSバケットに保存されます。
  • OAuth: Open Authorizationの略で、ユーザーが自分のアカウント情報を第三者のアプリケーションと安全に共有するための標準プロトコルです。makerelease ツールがGCSにファイルをアップロードする際に、認証のためにOAuthが使用されます。
  • go.googlecode.com/files: 以前GoのリリースファイルがホストされていたGoogle Codeのファイルホスティングサービス。このコミットの変更前は、makerelease が直接ここにファイルをアップロードしていました。
  • golang.org/dl/upload: このコミットで導入された新しいエンドポイント。makerelease ツールが、アップロードされたファイルに関するメタデータをJSON形式でHTTP POSTリクエストとして送信する先のURLです。このエンドポイントが受け取った情報に基づいて、Goの公式ダウンロードページが動的に更新されます。
  • SHA1 チェックサム: Secure Hash Algorithm 1の略で、データの完全性を検証するために使用される暗号学的ハッシュ関数です。ファイルの内容から一意の短い文字列(ハッシュ値)を生成し、ダウンロードされたファイルが破損していないか、改ざんされていないかを確認するために利用されます。
  • JSON (JavaScript Object Notation): 軽量なデータ交換フォーマット。人間にとっても読み書きしやすく、機械にとっても解析しやすい形式です。このコミットでは、アップロードされるファイルのメタデータをJSON形式で新しいダウンロードページに送信しています。
  • HTTP POSTリクエスト: Webサーバーにデータを送信するためのHTTPメソッドの一つ。このコミットでは、JSON形式のメタデータを golang.org/dl/upload エンドポイントに送信するために使用されます。

これらの知識は、コミットの変更がGoのリリースインフラストラクチャ全体にどのように影響するかを理解する上で不可欠です。

技術的詳細

このコミットの主要な技術的変更点は、misc/makerelease/makerelease.go ファイルにおけるアップロード処理のロジックの変更です。

  1. 新しいアップロードURLの導入:

    • 以前はハードコードされていた uploadURL = "https://go.googlecode.com/files" が削除されました。
    • 代わりに、flag パッケージを使用して uploadURL という新しいコマンドラインフラグが追加され、デフォルト値として http://golang.org/dl/upload が設定されました。これにより、アップロード先のURLを柔軟に設定できるようになりました。
    • defaultUploadURL 定数が http://golang.org/dl/upload として定義されました。
  2. ファイルの読み込みとSHA1チェックサムの計算:

    • Upload 関数内で、まずアップロード対象のファイルを ioutil.ReadFile(filename) でメモリに読み込みます。
    • 読み込んだファイルの内容に対して sha1.Sum(file) を実行し、SHA1チェックサムを計算します。このチェックサムは、ダウンロードされたファイルの整合性検証に利用されます。
  3. ファイルメタデータ構造 File の定義:

    • アップロード情報をJSON形式で送信するために、新しい構造体 File が定義されました。
    • File 構造体には以下のフィールドが含まれます:
      • Filename (string): アップロードされるファイルの名前(例: go1.3.linux-amd64.tar.gz)。
      • OS (string): 対象オペレーティングシステム(例: linux)。
      • Arch (string): 対象アーキテクチャ(例: amd64)。
      • Version (string): Goのバージョン(例: go1.3)。
      • Checksum (string): 計算されたSHA1チェックサム。datastore:",noindex" タグが付与されており、Datastoreに保存される際にこのフィールドがインデックスされないことを示唆しています(これは新しいダウンロードページがDatastoreを使用している可能性を示唆します)。
      • Kind (string): ファイルの種類("archive", "installer", "source")。
  4. ファイル種類の自動判別:

    • Upload 関数内で、ファイルの拡張子やビルド設定に基づいて Kind フィールドが自動的に判別されます。
      • b.Sourcetrue の場合(ソースアーカイブの場合)は "source"
      • ファイル名が .tar.gz または .zip で終わる場合は "archive"
      • ファイル名が .msi または .pkg で終わる場合は "installer"
      • それ以外は "unknown"
  5. JSONデータの生成とHTTP POSTリクエスト:

    • File 構造体のインスタンスが作成され、必要なメタデータが設定されます。
    • json.Marshal(File{...}) を使用して、この構造体がJSONバイト配列に変換されます。
    • 新しいアップロードURL (*uploadURL) と、builderKey という認証キーを含むクエリパラメータが結合され、最終的なリクエストURLが構築されます。
    • http.Post(u, "application/json", bytes.NewReader(req)) を使用して、JSONデータが新しいダウンロードページのエンドポイントにHTTP POSTリクエストとして送信されます。"application/json" ヘッダーは、送信されるデータのMIMEタイプがJSONであることを示します。
  6. エラーハンドリングの改善:

    • アップロード処理中のエラーメッセージがより詳細になり、どのファイルでエラーが発生したかを明確に報告するようになりました。例えば、log.Printf("%s: %v", targ, err)log.Printf("uploading %s: %v", targ, err) に変更され、return errreturn fmt.Errorf("uploading %s: %v", targ, err) に変更されています。
    • HTTP POSTリクエストのレスポンスステータスコードが http.StatusOK (200 OK) でない場合、エラーとして報告されます。

これらの変更により、makerelease ツールは単にファイルをGCSにアップロードするだけでなく、そのアップロードに関する豊富なメタデータを新しいダウンロードページシステムに通知する役割も担うようになりました。これにより、ダウンロードページはアップロードされたファイルを自動的に認識し、ユーザーに表示できるようになります。

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

misc/makerelease/makerelease.go ファイルにおける主要な変更箇所は以下の通りです。

--- a/misc/makerelease/makerelease.go
+++ b/misc/makerelease/makerelease.go
@@ -12,12 +12,15 @@ import (
 	"bufio"
 	"bytes"
 	"compress/gzip"
+	"crypto/sha1"
+	"encoding/json"
 	"flag"
 	"fmt"
 	"io"
 	"io/ioutil"
 	"log"
 	"net/http"
+	"net/url"
 	"os"
 	"os/exec"
 	"path"
@@ -43,12 +46,13 @@ var (
 	staticToolchain = flag.Bool("static", true, "try to build statically linked toolchain (only supported on ELF targets)")
 	tokenCache      = flag.String("token", defaultCacheFile, "Authentication token cache file")
 	storageBucket   = flag.String("bucket", "golang", "Cloud Storage Bucket")
+	uploadURL       = flag.String("upload_url", defaultUploadURL, "Upload URL")
  
 	defaultCacheFile = filepath.Join(os.Getenv("HOME"), ".makerelease-request-token")
+	defaultUploadURL = "http://golang.org/dl/upload"
 )
  
 const (
-	uploadURL      = "https://go.googlecode.com/files"
 	blogPath       = "code.google.com/p/go.blog"
 	toolPath       = "code.google.com/p/go.tools"
 	tourPath       = "code.google.com/p/go-tour"
@@ -167,7 +171,7 @@ func main() {
 				continue
 			}
 			if err := b.Upload(version, targ); err != nil {
-				log.Printf("%s: %v", targ, err)
+				log.Printf("uploading %s: %v", targ, err)
 			}
 			continue
 		}
@@ -455,7 +459,7 @@ func (b *Build) Do() error {
 		for _, targ := range targs {
 			err = b.Upload(version, targ)
 			if err != nil {
-				return err
+				return fmt.Errorf("uploading %s: %v", targ, err)
 			}
 		}
 	}
@@ -653,28 +657,67 @@ func (b *Build) env() []string {
 }
  
 func (b *Build) Upload(version string, filename string) error {
-	svc, err := storage.New(oauthClient)
+	file, err := ioutil.ReadFile(filename)
 	if err != nil {
 		return err
 	}
  
+	svc, err := storage.New(oauthClient)
+	if err != nil {
+		return err
+	}
 	obj := &storage.Object{
 		Acl:  []*storage.ObjectAccessControl{{Entity: "allUsers", Role: "READER"}},
 		Name: filename,
 	}
-	f, err := os.Open(filename)
+	_, err = svc.Objects.Insert(*storageBucket, obj).Media(bytes.NewReader(file)).Do()
 	if err != nil {
 		return err
 	}
-	defer f.Close()
-	_, err = svc.Objects.Insert(*storageBucket, obj).Media(f).Do()
+
+	sum := fmt.Sprintf("%x", sha1.Sum(file))
+	kind := "unknown"
+	switch {
+	case b.Source:
+		kind = "source"
+	case strings.HasSuffix(filename, ".tar.gz"), strings.HasSuffix(filename, ".zip"):
+		kind = "archive"
+	case strings.HasSuffix(filename, ".msi"), strings.HasSuffix(filename, ".pkg"):
+		kind = "installer"
+	}
+	req, err := json.Marshal(File{
+		Filename: filename,
+		Version:  version,
+		OS:       b.OS,
+		Arch:     b.Arch,
+		Checksum: sum,
+		Kind:     kind,
+	})
+	if err != nil {
+		return err
+	}
+	u := fmt.Sprintf("%s?%s", *uploadURL, url.Values{"key": []string{builderKey}}.Encode())
+	resp, err := http.Post(u, "application/json", bytes.NewReader(req))
 	if err != nil {
 		return err
 	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("upload status: %v", resp.Status)
+	}
  
 	return nil
 }
  
+type File struct {
+	Filename string
+	OS       string
+	Arch     string
+	Version  string
+	Checksum string `datastore:",noindex"`
+	Kind     string // "archive", "installer", "source"
+}
+
 func setupOAuthClient() {
 	config := &oauth.Config{
 		ClientId:     "999119582588-h7kpj5pcm6d9solh5lgrbusmvvk4m9dn.apps.googleusercontent.com",

コアとなるコードの解説

このコミットの核心は、Upload 関数内のロジックの変更と、新しい File 構造体の導入にあります。

  1. Upload 関数の変更:

    • ファイルの読み込みとGCSへのアップロード: 以前は os.Open でファイルを開き、そのファイルハンドルをGCSアップロードに渡していましたが、変更後は ioutil.ReadFile でファイル全体をメモリに読み込んでいます。これにより、ファイルの内容全体にアクセスできるようになり、後続のSHA1チェックサム計算が可能になります。GCSへのアップロード自体は svc.Objects.Insert(*storageBucket, obj).Media(bytes.NewReader(file)).Do() で行われ、これは以前とほぼ同じですが、bytes.NewReader(file) を使用してメモリ上のファイル内容を渡しています。
    • SHA1チェックサムの計算: sum := fmt.Sprintf("%x", sha1.Sum(file)) の行で、メモリに読み込んだファイルの内容からSHA1ハッシュを計算しています。このハッシュ値は、ダウンロードされたファイルの整合性を検証するために非常に重要です。
    • ファイル種類の判別: switch ステートメントを使用して、アップロードされるファイルの種類(ソース、アーカイブ、インストーラー)を自動的に判別し、kind 変数に設定しています。これは、ダウンロードページでファイルを適切に分類・表示するために使用されます。
    • メタデータのJSON化: json.Marshal(File{...}) の行で、ファイル名、バージョン、OS、アーキテクチャ、チェックサム、種類といったメタデータを File 構造体に格納し、それをJSON形式のバイト配列に変換しています。
    • 新しいダウンロードページへの報告: http.Post を使用して、生成されたJSONデータを *uploadURL(デフォルトでは http://golang.org/dl/upload)に送信しています。このHTTP POSTリクエストには、認証のための builderKey もクエリパラメータとして含まれています。これにより、新しいダウンロードページシステムは、アップロードされたファイルに関する情報をリアルタイムで受け取り、自身のデータベースを更新することができます。
    • エラーハンドリング: HTTPレスポンスのステータスコードが StatusOK でない場合、エラーを返すことで、アップロード報告が成功したかどうかを確認しています。
  2. File 構造体の導入:

    • この新しい構造体は、Goのリリースファイルに関する標準化されたメタデータ形式を提供します。これにより、makerelease ツールと新しいダウンロードページシステムの間で、ファイル情報を一貫した方法で交換できるようになります。Checksum フィールドに datastore:",noindex" タグが付いていることから、この情報がGoogle Cloud Datastoreに保存され、かつこのフィールドでは検索インデックスが不要であることが示唆されます。

これらの変更により、Goのリリースプロセスは、単にファイルをストレージに置くだけでなく、そのファイルに関する豊富な情報を新しいダウンロードページに自動的に通知する、より洗練されたシステムへと進化しました。

関連リンク

参考にした情報源リンク

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

このコミットは、Go言語のリリースプロセスで使用される misc/makerelease ツールに対する変更です。具体的には、新しいダウンロードページへのアップロード報告メカニズムを導入し、リリースされたバイナリやソースアーカイブの情報を適切に新しいシステムに送信するように更新しています。これにより、Goの公式ダウンロードページがより正確かつ効率的に更新されるようになります。

コミット

commit 865904f6d8c1dd80b1a203c531d0eb03e7f403f7
Author: Andrew Gerrand <adg@golang.org>
Date:   Mon Jun 2 12:46:03 2014 +1000

    misc/makerelease: report uploads to the new downloads page
    
    LGTM=bradfitz
    R=golang-codereviews, bradfitz
    CC=golang-codereviews
    https://golang.org/cl/102040047

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

https://github.com/golang/go/commit/865904f6d8c1dd80b1a203c531d0eb03e7f403f7

元コミット内容

このコミットは、misc/makerelease ツールが、Goの新しいダウンロードページにアップロード情報を報告するように変更するものです。以前は go.googlecode.com/files に直接ファイルをアップロードしていましたが、新しいダウンロードページでは、アップロードされたファイルに関するメタデータ(ファイル名、バージョン、OS、アーキテクチャ、チェックサム、種類など)をHTTP POSTリクエストで特定のURL (golang.org/dl/upload) に送信することで、ダウンロードページが動的に更新される仕組みに移行しました。

変更の背景

Go言語のリリースプロセスは、新しいバージョンのGoが公開されるたびに、そのバイナリやソースコードをユーザーがダウンロードできるようにするための重要な手順です。以前は、Google Codeのファイルホスティングサービス(go.googlecode.com/files)を利用してリリースファイルを公開していました。しかし、このシステムは静的なファイルホスティングであり、ダウンロードページの情報を手動で更新する必要があるか、あるいは限定的な自動化しか提供していなかった可能性があります。

このコミットが行われた2014年頃は、Google Codeが徐々にその役割を終え、GitHubなどの他のプラットフォームへの移行が進んでいた時期と重なります。Goプロジェクトも、より柔軟で動的なダウンロードページを提供するために、インフラストラクチャの変更を計画していました。

この変更の主な背景は以下の通りです。

  1. ダウンロードページの刷新: ユーザーエクスペリエンスの向上と、より詳細な情報(チェックサム、ファイルの種類など)を提供できる新しいダウンロードページへの移行。
  2. 自動化の強化: リリースファイルのアップロードと同時に、ダウンロードページの情報も自動的に更新されるようにすることで、手動での作業を減らし、エラーのリスクを低減する。
  3. Google Codeからの移行: Google Codeのサービス終了を見据え、Goのリリースインフラをより持続可能なものにする必要があった。新しいシステムでは、Google Cloud Storageにファイルを保存し、そのメタデータを新しいダウンロードページに報告する形に変わりました。

これにより、Goのリリース管理がより堅牢で自動化されたものになり、ユーザーは常に最新かつ正確なダウンロード情報にアクセスできるようになりました。

前提知識の解説

このコミットを理解するためには、以下の技術的背景知識が役立ちます。

  • Go言語のリリースプロセス: Go言語の新しいバージョンがリリースされる際の一連の作業。これには、ソースコードのビルド、各種プラットフォーム向けバイナリの生成、テスト、そして生成されたファイルの公開が含まれます。misc/makerelease はこのプロセスの一部を自動化するツールです。
  • misc/makerelease ツール: Goプロジェクトの misc ディレクトリにある内部ツールで、Goの公式リリースをビルドし、公開するためのスクリプトやユーティリティが含まれています。このツールは、Goのソースからコンパイラやツールチェインをビルドし、様々なOS/アーキテクチャ向けのバイナリパッケージを作成し、それらをストレージにアップロードする役割を担っています。
  • Google Cloud Storage (GCS): Googleが提供するオブジェクトストレージサービス。大量のデータを保存し、世界中からアクセス可能にするためのスケーラブルなインフラを提供します。Goのリリースファイルは、このGCSバケットに保存されます。
  • OAuth: Open Authorizationの略で、ユーザーが自分のアカウント情報を第三者のアプリケーションと安全に共有するための標準プロトコルです。makerelease ツールがGCSにファイルをアップロードする際に、認証のためにOAuthが使用されます。
  • go.googlecode.com/files: 以前GoのリリースファイルがホストされていたGoogle Codeのファイルホスティングサービス。このコミットの変更前は、makerelease が直接ここにファイルをアップロードしていました。
  • golang.org/dl/upload: このコミットで導入された新しいエンドポイント。makerelease ツールが、アップロードされたファイルに関するメタデータをJSON形式でHTTP POSTリクエストとして送信する先のURLです。このエンドポイントが受け取った情報に基づいて、Goの公式ダウンロードページが動的に更新されます。
  • SHA1 チェックサム: Secure Hash Algorithm 1の略で、データの完全性を検証するために使用される暗号学的ハッシュ関数です。ファイルの内容から一意の短い文字列(ハッシュ値)を生成し、ダウンロードされたファイルが破損していないか、改ざんされていないかを確認するために利用されます。
  • JSON (JavaScript Object Notation): 軽量なデータ交換フォーマット。人間にとっても読み書きしやすく、機械にとっても解析しやすい形式です。このコミットでは、アップロードされるファイルのメタデータをJSON形式で新しいダウンロードページに送信しています。
  • HTTP POSTリクエスト: Webサーバーにデータを送信するためのHTTPメソッドの一つ。このコミットでは、JSON形式のメタデータを golang.org/dl/upload エンドポイントに送信するために使用されます。

これらの知識は、コミットの変更がGoのリリースインフラストラクチャ全体にどのように影響するかを理解する上で不可欠です。

技術的詳細

このコミットの主要な技術的変更点は、misc/makerelease/makerelease.go ファイルにおけるアップロード処理のロジックの変更です。

  1. 新しいアップロードURLの導入:

    • 以前はハードコードされていた uploadURL = "https://go.googlecode.com/files" が削除されました。
    • 代わりに、flag パッケージを使用して uploadURL という新しいコマンドラインフラグが追加され、デフォルト値として http://golang.org/dl/upload が設定されました。これにより、アップロード先のURLを柔軟に設定できるようになりました。
    • defaultUploadURL 定数が http://golang.org/dl/upload として定義されました。
  2. ファイルの読み込みとSHA1チェックサムの計算:

    • Upload 関数内で、まずアップロード対象のファイルを ioutil.ReadFile(filename) でメモリに読み込みます。
    • 読み込んだファイルの内容に対して sha1.Sum(file) を実行し、SHA1チェックサムを計算します。このチェックサムは、ダウンロードされたファイルの整合性検証に利用されます。
  3. ファイルメタデータ構造 File の定義:

    • アップロード情報をJSON形式で送信するために、新しい構造体 File が定義されました。
    • File 構造体には以下のフィールドが含まれます:
      • Filename (string): アップロードされるファイルの名前(例: go1.3.linux-amd64.tar.gz)。
      • OS (string): 対象オペレーティングシステム(例: linux)。
      • Arch (string): 対象アーキテクチャ(例: amd64)。
      • Version (string): Goのバージョン(例: go1.3)。
      • Checksum (string): 計算されたSHA1チェックサム。datastore:",noindex" タグが付与されており、Datastoreに保存される際にこのフィールドがインデックスされないことを示唆しています(これは新しいダウンロードページがDatastoreを使用している可能性を示唆します)。
      • Kind (string): ファイルの種類("archive", "installer", "source")。
  4. ファイル種類の自動判別:

    • Upload 関数内で、ファイルの拡張子やビルド設定に基づいて Kind フィールドが自動的に判別されます。
      • b.Sourcetrue の場合(ソースアーカイブの場合)は "source"
      • ファイル名が .tar.gz または .zip で終わる場合は "archive"
      • ファイル名が .msi または .pkg で終わる場合は "installer"
      • それ以外は "unknown"
  5. JSONデータの生成とHTTP POSTリクエスト:

    • File 構造体のインスタンスが作成され、必要なメタデータが設定されます。
    • json.Marshal(File{...}) を使用して、この構造体がJSONバイト配列に変換されます。
    • 新しいアップロードURL (*uploadURL) と、builderKey という認証キーを含むクエリパラメータが結合され、最終的なリクエストURLが構築されます。
    • http.Post(u, "application/json", bytes.NewReader(req)) を使用して、JSONデータが新しいダウンロードページのエンドポイントにHTTP POSTリクエストとして送信されます。"application/json" ヘッダーは、送信されるデータのMIMEタイプがJSONであることを示します。
  6. エラーハンドリングの改善:

    • アップロード処理中のエラーメッセージがより詳細になり、どのファイルでエラーが発生したかを明確に報告するようになりました。例えば、log.Printf("%s: %v", targ, err)log.Printf("uploading %s: %v", targ, err) に変更され、return errreturn fmt.Errorf("uploading %s: %v", targ, err) に変更されています。
    • HTTP POSTリクエストのレスポンスステータスコードが http.StatusOK (200 OK) でない場合、エラーとして報告されます。

これらの変更により、makerelease ツールは単にファイルをGCSにアップロードするだけでなく、そのアップロードに関する豊富なメタデータを新しいダウンロードページシステムに通知する役割も担うようになりました。これにより、ダウンロードページはアップロードされたファイルを自動的に認識し、ユーザーに表示できるようになります。

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

misc/makerelease/makerelease.go ファイルにおける主要な変更箇所は以下の通りです。

--- a/misc/makerelease/makerelease.go
+++ b/misc/makerelease/makerelease.go
@@ -12,12 +12,15 @@ import (
 	"bufio"
 	"bytes"
 	"compress/gzip"
+	"crypto/sha1"
+	"encoding/json"
 	"flag"
 	"fmt"
 	"io"
 	"io/ioutil"
 	"log"
 	"net/http"
+	"net/url"
 	"os"
 	"os/exec"
 	"path"
@@ -43,12 +46,13 @@ var (
 	staticToolchain = flag.Bool("static", true, "try to build statically linked toolchain (only supported on ELF targets)")
 	tokenCache      = flag.String("token", defaultCacheFile, "Authentication token cache file")
 	storageBucket   = flag.String("bucket", "golang", "Cloud Storage Bucket")
+	uploadURL       = flag.String("upload_url", defaultUploadURL, "Upload URL")
  
 	defaultCacheFile = filepath.Join(os.Getenv("HOME"), ".makerelease-request-token")
+	defaultUploadURL = "http://golang.org/dl/upload"
 )
  
 const (
-	uploadURL      = "https://go.googlecode.com/files"
 	blogPath       = "code.google.com/p/go.blog"
 	toolPath       = "code.google.com/p/go.tools"
 	tourPath       = "code.google.com/p/go-tour"
@@ -167,7 +171,7 @@ func main() {
 				continue
 			}
 			if err := b.Upload(version, targ); err != nil {
-				log.Printf("%s: %v", targ, err)
+				log.Printf("uploading %s: %v", targ, err)
 			}
 			continue
 		}
@@ -455,7 +459,7 @@ func (b *Build) Do() error {
 		for _, targ := range targs {
 			err = b.Upload(version, targ)
 			if err != nil {
-				return err
+				return fmt.Errorf("uploading %s: %v", targ, err)
 			}
 		}
 	}
@@ -653,28 +657,67 @@ func (b *Build) env() []string {
 }
  
 func (b *Build) Upload(version string, filename string) error {
-	svc, err := storage.New(oauthClient)
+	file, err := ioutil.ReadFile(filename)
 	if err != nil {
 		return err
 	}
  
+	svc, err := storage.New(oauthClient)
+	if err != nil {
+		return err
+	}
 	obj := &storage.Object{
 		Acl:  []*storage.ObjectAccessControl{{Entity: "allUsers", Role: "READER"}},
 		Name: filename,
 	}
-	f, err := os.Open(filename)
+	_, err = svc.Objects.Insert(*storageBucket, obj).Media(bytes.NewReader(file)).Do()
 	if err != nil {
 		return err
 	}
-	defer f.Close()
-	_, err = svc.Objects.Insert(*storageBucket, obj).Media(f).Do()
+
+	sum := fmt.Sprintf("%x", sha1.Sum(file))
+	kind := "unknown"
+	switch {
+	case b.Source:
+		kind = "source"
+	case strings.HasSuffix(filename, ".tar.gz"), strings.HasSuffix(filename, ".zip"):
+		kind = "archive"
+	case strings.HasSuffix(filename, ".msi"), strings.HasSuffix(filename, ".pkg"):
+		kind = "installer"
+	}
+	req, err := json.Marshal(File{
+		Filename: filename,
+		Version:  version,
+		OS:       b.OS,
+		Arch:     b.Arch,
+		Checksum: sum,
+		Kind:     kind,
+	})
+	if err != nil {
+		return err
+	}
+	u := fmt.Sprintf("%s?%s", *uploadURL, url.Values{"key": []string{builderKey}}.Encode())
+	resp, err := http.Post(u, "application/json", bytes.NewReader(req))
 	if err != nil {
 		return err
 	}
+	defer resp.Body.Close()
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("upload status: %v", resp.Status)
+	}
  
 	return nil
 }
  
+type File struct {
+	Filename string
+	OS       string
+	Arch     string
+	Version  string
+	Checksum string `datastore:",noindex"`
+	Kind     string // "archive", "installer", "source"
+}
+
 func setupOAuthClient() {
 	config := &oauth.Config{
 		ClientId:     "999119582588-h7kpj5pcm6d9solh5lgrbusmvvk4m9dn.apps.googleusercontent.com",

コアとなるコードの解説

このコミットの核心は、Upload 関数内のロジックの変更と、新しい File 構造体の導入にあります。

  1. Upload 関数の変更:

    • ファイルの読み込みとGCSへのアップロード: 以前は os.Open でファイルを開き、そのファイルハンドルをGCSアップロードに渡していましたが、変更後は ioutil.ReadFile でファイル全体をメモリに読み込んでいます。これにより、ファイルの内容全体にアクセスできるようになり、後続のSHA1チェックサム計算が可能になります。GCSへのアップロード自体は svc.Objects.Insert(*storageBucket, obj).Media(bytes.NewReader(file)).Do() で行われ、これは以前とほぼ同じですが、bytes.NewReader(file) を使用してメモリ上のファイル内容を渡しています。
    • SHA1チェックサムの計算: sum := fmt.Sprintf("%x", sha1.Sum(file)) の行で、メモリに読み込んだファイルの内容からSHA1ハッシュを計算しています。このハッシュ値は、ダウンロードされたファイルの整合性を検証するために非常に重要です。
    • ファイル種類の判別: switch ステートメントを使用して、アップロードされるファイルの種類(ソース、アーカイブ、インストーラー)を自動的に判別し、kind 変数に設定しています。これは、ダウンロードページでファイルを適切に分類・表示するために使用されます。
    • メタデータのJSON化: json.Marshal(File{...}) の行で、ファイル名、バージョン、OS、アーキテクチャ、チェックサム、種類といったメタデータを File 構造体に格納し、それをJSON形式のバイト配列に変換しています。
    • 新しいダウンロードページへの報告: http.Post を使用して、生成されたJSONデータを *uploadURL(デフォルトでは http://golang.org/dl/upload)に送信しています。このHTTP POSTリクエストには、認証のための builderKey もクエリパラメータとして含まれています。これにより、新しいダウンロードページシステムは、アップロードされたファイルに関する情報をリアルタイムで受け取り、自身のデータベースを更新することができます。
    • エラーハンドリング: HTTPレスポンスのステータスコードが StatusOK でない場合、エラーを返すことで、アップロード報告が成功したかどうかを確認しています。
  2. File 構造体の導入:

    • この新しい構造体は、Goのリリースファイルに関する標準化されたメタデータ形式を提供します。これにより、makerelease ツールと新しいダウンロードページシステムの間で、ファイル情報を一貫した方法で交換できるようになります。Checksum フィールドに datastore:",noindex" タグが付いていることから、この情報がGoogle Cloud Datastoreに保存され、かつこのフィールドでは検索インデックスが不要であることが示唆されます。

これらの変更により、Goのリリースプロセスは、単にファイルをストレージに置くだけでなく、そのファイルに関する豊富な情報を新しいダウンロードページに自動的に通知する、より洗練されたシステムへと進化しました。

関連リンク

参考にした情報源リンク