[インデックス 19464] ファイルの概要
このコミットは、Go言語のリリースプロセスで使用される misc/makerelease/makerelease.go
ファイルに対する変更です。makerelease
ツールは、Goのリリースバイナリやソースコードパッケージをビルドし、公開するためのユーティリティです。このファイルは、ビルドされた成果物をGoogleのインフラストラクチャにアップロードするロジックを含んでいます。
コミット
misc/makerelease: upload files to Google Cloud Storage
LGTM=bradfitz
R=jasonhall, bradfitz
CC=golang-codereviews
https://golang.org/cl/91700047
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/0627fa84b773fbb38b28727d99e7e35e7345ca05
元コミット内容
このコミットの元の内容は、misc/makerelease
ツールがリリースファイルをGoogle Cloud Storageにアップロードするように変更することです。
変更の背景
この変更の主な背景には、Googleのプラットフォーム戦略の進化があります。コミットが行われた2014年当時、Googleはオープンソースプロジェクトのホスティングサービスである「Google Code Project Hosting」を提供していました。Go言語のリリース成果物も、このGoogle Codeを通じて配布されていた可能性があります。
しかし、GitHubのようなよりモダンで機能豊富な代替プラットフォームの台頭、およびスパムや悪用への対応の困難さから、Googleは2015年から2016年にかけてGoogle Code Project Hostingの段階的な廃止を進めました。
このような状況下で、Goのリリースプロセスも、将来的に廃止される可能性のあるGoogle Codeのアップロード機能に依存し続けることは得策ではありませんでした。そこで、より堅牢でスケーラブルなGoogle Cloud Platformのサービスである「Google Cloud Storage (GCS)」への移行が決定されました。GCSは、大量のデータを安全かつ効率的に保存・配信するためのオブジェクトストレージサービスであり、Goのリリース成果物の配布先としてより適切でした。
このコミットは、Google CodeのアップロードAPIからGoogle Cloud StorageのAPIへの切り替えを実装することで、Goのリリースインフラストラクチャを将来にわたって持続可能にするための重要なステップでした。
前提知識の解説
Google Code (Project Hosting) とそのアップロード機能
Google Code Project Hostingは、Googleが提供していた無料のオープンソースプロジェクトホスティングサービスです。バージョン管理システム(SVN、Mercurial、Git)、イシュートラッカー、Wiki、ダウンロードセクションなどを提供していました。プロジェクトの成果物を配布するために、ダウンロードセクションにファイルをアップロードする機能がありました。このアップロードは、Basic認証とmultipart/form-data形式のHTTP POSTリクエストを通じて行われることが一般的でした。しかし、前述の通り、このサービスは2015年から2016年にかけて廃止されました。
Google Cloud Storage (GCS)
Google Cloud Storage (GCS) は、Google Cloud Platform (GCP) が提供する、スケーラブルで耐久性の高いオブジェクトストレージサービスです。ファイル、画像、動画、バックアップなど、あらゆる種類の非構造化データを保存できます。データは「バケット」と呼ばれるコンテナに「オブジェクト」として保存されます。GCSは、高い可用性、堅牢なセキュリティ(保存時の暗号化、IAMによるアクセス制御)、および世界中のどこからでもアクセス可能なグローバルなインフラストラクチャを提供します。APIを通じてプログラムから簡単にアクセス・操作できるため、アプリケーションのバックエンドストレージとして広く利用されています。
OAuth2
OAuth 2.0は、認可のための業界標準プロトコルです。ユーザーの認証情報をクライアントアプリケーションに公開することなく、リソースサーバー上の保護されたリソースへの限定的なアクセスを許可します。OAuth2の主要な役割は以下の通りです。
- リソースオーナー: 保護されたリソースへのアクセスを許可できるエンティティ(例: ユーザー)。
- クライアント: リソースオーナーに代わって保護されたリソースへのアクセスを要求するアプリケーション。
- 認可サーバー: リソースオーナーを認証し、リソースオーナーの承認を得てクライアントにアクセストークンを発行するサーバー。
- リソースサーバー: 保護されたリソースをホストし、アクセストークンを使用して保護されたリソース要求を受け入れて応答できるサーバー。
一般的なフロー(認可コードグラント)では、クライアントはユーザーを認可サーバーにリダイレクトし、ユーザーがアクセスを承認すると、認可サーバーは認可コードをクライアントに返します。クライアントはこの認可コードと自身のクライアントシークレットを使用して、認可サーバーからアクセストークンを取得します。その後、クライアントはこのアクセストークンをリソースサーバーへのリクエストに含めることで、保護されたリソースにアクセスします。
Go言語におけるGoogle APIクライアントライブラリの進化と役割
Go言語でGoogleのサービスと連携するための公式ライブラリは、時間の経過とともに進化してきました。
code.google.com/p/goauth2/oauth
: このコミットで導入されているライブラリは、Googleが提供していた初期のGo向けOAuth2クライアントライブラリです。これは、Google APIへの認証済みリクエストを行うための基盤を提供していました。code.google.com/p/google-api-go-client/storage/v1beta2
: 同様に、このコミットで導入されているのは、Google Cloud StorageのAPIをGoから操作するための初期のクライアントライブラリです。v1beta2
はAPIのバージョンを示しています。golang.org/x/oauth2
:code.google.com/p/goauth2/oauth
は、後にGoの公式エクステンションリポジトリであるgolang.org/x
に移動し、より汎用的なOAuth2クライアントライブラリとしてgolang.org/x/oauth2
となりました。これは現在、GoアプリケーションでOAuth2認証を実装する際の標準的な選択肢です。cloud.google.com/go/storage
:code.google.com/p/google-api-go-client/storage
は、後にGoogle CloudのGo向けクライアントライブラリ群の一部としてcloud.google.com/go/storage
に統合・再構築されました。これは、Google Cloud Storageを操作するための最新かつ推奨されるライブラリです。
このコミットは、これらのライブラリがまだ code.google.com/p
パスにあった時期に行われたものであり、当時の最新のGoogle API連携手法を取り入れたものです。
makerelease
ツールの役割
makerelease
ツールは、Goプロジェクトのリリースプロセスを自動化するためのGoプログラムです。具体的には、Goのソースコードから様々なプラットフォーム向けのバイナリ(Windows、Linux、macOSなど)やパッケージ(tar.gz、zip、msi、pkgなど)をビルドし、それらを指定された場所にアップロードする機能を持っています。このツールは、Goの公式リリースを生成する際に内部的に使用されていました。
技術的詳細
このコミットの核心は、Goのリリース成果物のアップロードメカニズムを、Google Codeの旧来のAPIからGoogle Cloud Storage (GCS) のAPIへと完全に切り替える点にあります。
旧来のアップロード方法(変更前)
変更前の Upload
関数は、Basic認証と multipart/form-data
を使用して、HTTP POSTリクエストをGoogle CodeのアップロードURLに送信していました。
- メタデータの準備: アップロードするファイルの概要、OS、アーキテクチャ、ファイルタイプなどのメタデータを文字列として構築していました。
- ファイルの読み込み: アップロード対象のファイルを
os.Open
で開き、その内容を読み込む準備をしていました。 - multipartペイロードの構築:
mime/multipart
パッケージを使用して、HTTPリクエストのボディをmultipart/form-data
形式で構築していました。これには、メタデータフィールド(summary
,label
)とファイルの内容が含まれていました。 - HTTPリクエストの送信:
http.NewRequest
でPOSTリクエストを作成し、Authorization
ヘッダーにBasic認証のトークン(ユーザー名とパスワードをBase64エンコードしたもの)を設定していました。 - エラーハンドリング: HTTPレスポンスのステータスコードを確認し、エラーが発生した場合は標準エラー出力に詳細を出力していました。
この方法は、手動でのHTTPリクエスト構築が必要であり、認証情報(ユーザー名とパスワード)を直接扱うため、セキュリティ上のリスクも伴いました。
新しいアップロード方法(変更後)
変更後の Upload
関数は、Googleが提供するGo言語用の公式クライアントライブラリ code.google.com/p/google-api-go-client/storage/v1beta2
と、OAuth2認証ライブラリ code.google.com/p/goauth2/oauth
を利用しています。
-
OAuth2クライアントのセットアップ:
setupOAuthClient()
関数が新しく導入されました。この関数は、OAuth2の認可フローを管理し、Google APIへの認証済みリクエストを行うための*http.Client
インスタンス(oauthClient
)を生成します。oauth.Config
を使用して、クライアントID、クライアントシークレット、スコープ(storage.DevstorageRead_writeScope
、GCSへの読み書き権限)、認可URL、トークンURL、そしてトークンキャッシュファイル(.makerelease-request-token
)を設定します。- トークンキャッシュファイルが存在しない場合やトークンが期限切れの場合、ユーザーはブラウザでGoogleの認可URLにアクセスし、認証コードを取得してCLIに入力するよう求められます。これにより、アクセストークンとリフレッシュトークンが取得され、トークンキャッシュファイルに保存されます。
oauth.Transport
を使用して、認証済みのHTTPクライアントが作成されます。このクライアントは、以降のGCS APIリクエストに自動的にアクセストークンを付与します。
-
Google Cloud Storageサービスの初期化:
storage.New(oauthClient)
を呼び出すことで、OAuth2認証済みのHTTPクライアントを使用してGCSサービスクライアント(svc
)を初期化します。これにより、GCS APIへのアクセスが可能になります。
-
オブジェクトメタデータの設定:
storage.Object
構造体を使用して、アップロードするオブジェクトのメタデータを定義します。Acl: []*storage.ObjectAccessControl{{Entity: "allUsers", Role: "READER"}}
の設定は、アップロードされたファイルが「allUsers」(すべてのユーザー)に対して「READER」(読み取り)権限を持つことを意味します。これにより、公開ダウンロードが可能になります。Name: filename
は、GCSバケット内でオブジェクトが保存されるファイル名を指定します。
-
ファイルのアップロード:
os.Open(filename)
でアップロード対象のファイルを読み込み用に開きます。svc.Objects.Insert(*storageBucket, obj).Media(f).Do()
を呼び出すことで、実際にファイルをGCSにアップロードします。*storageBucket
は、アップロード先のGCSバケット名(デフォルトはgolang
)です。obj
は、上記で定義したオブジェクトメタデータです。Media(f)
は、アップロードするファイルのio.Reader
を指定します。Do()
は、APIリクエストを実行します。
この新しい方法は、Googleが提供する高レベルなクライアントライブラリを使用するため、開発者はHTTPリクエストの詳細や認証フローの複雑さを意識する必要がなくなります。OAuth2を使用することで、ユーザー名とパスワードを直接扱うことなく、より安全にAPIアクセスを管理できるようになります。また、GCSはスケーラビリティと耐久性に優れているため、Goのリリース配布インフラストラクチャとしてより堅牢な基盤を提供します。
コアとなるコードの変更箇所
misc/makerelease/makerelease.go
--- a/misc/makerelease/makerelease.go
+++ b/misc/makerelease/makerelease.go
@@ -12,13 +12,11 @@ import (
"bufio"
"bytes"
"compress/gzip"
- "encoding/base64"
"flag"
"fmt"
"io"
"io/ioutil"
"log"
- "mime/multipart"
"net/http"
"os"
"os/exec"
@@ -27,6 +25,9 @@ import (
"regexp"
"runtime"
"strings"
+\n
+\t"code.google.com/p/goauth2/oauth"
+\t"code.google.com/p/google-api-go-client/storage/v1beta2"
)
var (
@@ -40,8 +41,10 @@ var (
includeRace = flag.Bool("race", true, "build race detector packages")
versionOverride = flag.String("version", "", "override version name")
staticToolchain = flag.Bool("static", true, "try to build statically linked toolchain (only supported on ELF targets)")
+\ttokenCache = flag.String("token", defaultCacheFile, "Authentication token cache file")
+\tstorageBucket = flag.String("bucket", "golang", "Cloud Storage Bucket")
\n
-\tusername, password string // for Google Code upload
+\tdefaultCacheFile = filepath.Join(os.Getenv("HOME"), ".makerelease-request-token")
)\n
const (\
@@ -119,6 +122,12 @@ var staticLinkAvailable = []string{
\n var fileRe = regexp.MustCompile(`^(go[a-z0-9-.]+)\\.(src|([a-z0-9]+)-([a-z0-9]+)(?:-([a-z0-9.]+))?)\\.(tar\\.gz|zip|pkg|msi)$`)\n \n+// OAuth2-authenticated HTTP client used to make calls to Cloud Storage.\n+var oauthClient *http.Client\n+\n+// Builder key as specified in ~/.gobuildkey\n+var builderKey string\n+\n func main() {\n \tflag.Usage = func(){\n \t\tfmt.Fprintf(os.Stderr, "usage: %s [flags] targets...\\n", os.Args[0])\n@@ -135,7 +144,10 @@ func main() {\n \n \tif *upload {\n \t\tif err := readCredentials(); err != nil {\n-\t\t\tlog.Println("readCredentials:", err)\n+\t\t\tlog.Fatalln("readCredentials:", err)\n+\t\t}\n+\t\tif err := setupOAuthClient(); err != nil {\n+\t\t\tlog.Fatalln("setupOAuthClient:", err)\n \t\t}\n \t}\n \tfor _, targ := range flag.Args() {\n@@ -641,121 +653,56 @@ func (b *Build) env() []string {\n }\n \n func (b *Build) Upload(version string, filename string) error {\n-\t// Prepare upload metadata.\n-\tvar labels []string\n-\tos_, arch := b.OS, b.Arch\n-\tswitch b.Arch {\n-\tcase "386":\n-\t\tarch = "x86 32-bit"\n-\tcase "amd64":\n-\t\tarch = "x86 64-bit"\n-\t}\n-\tif arch != "" {\n-\t\tlabels = append(labels, "Arch-"+b.Arch)\n-\t}\n-\tvar opsys, ftype string // labels\n-\tswitch b.OS {\n-\tcase "linux":\n-\t\tos_ = "Linux"\n-\t\topsys = "Linux"\n-\tcase "freebsd":\n-\t\tos_ = "FreeBSD"\n-\t\topsys = "FreeBSD"\n-\tcase "darwin":\n-\t\tos_ = "Mac OS X"\n-\t\topsys = "OSX"\n-\tcase "netbsd":\n-\t\tos_ = "NetBSD"\n-\t\topsys = "NetBSD"\n-\tcase "windows":\n-\t\tos_ = "Windows"\n-\t\topsys = "Windows"\n-\t}\n-\tsummary := fmt.Sprintf("%s %s (%s)", version, os_, arch)\n-\tswitch {\n-\tcase strings.HasSuffix(filename, ".msi"):\n-\t\tftype = "Installer"\n-\t\tsummary += " MSI installer"\n-\tcase strings.HasSuffix(filename, ".pkg"):\n-\t\tftype = "Installer"\n-\t\tsummary += " PKG installer"\n-\tcase strings.HasSuffix(filename, ".zip"):\n-\t\tftype = "Archive"\n-\t\tsummary += " ZIP archive"\n-\tcase strings.HasSuffix(filename, ".tar.gz"):\n-\t\tftype = "Archive"\n-\t\tsummary += " tarball"\n-\t}\n-\tif b.Source {\n-\t\tftype = "Source"\n-\t\tsummary = fmt.Sprintf("%s (source only)", version)\n-\t}\n-\tif opsys != "" {\n-\t\tlabels = append(labels, "OpSys-"+opsys)\n-\t}\n-\tif ftype != "" {\n-\t\tlabels = append(labels, "Type-"+ftype)\n-\t}\n-\tif b.Label != "" {\n-\t\tlabels = append(labels, b.Label)\n-\t}\n-\tif *addLabel != "" {\n-\t\tlabels = append(labels, *addLabel)\n-\t}\n-\t// Put "Go" prefix on summary when it doesn't already begin with "go".\n-\tif !strings.HasPrefix(strings.ToLower(summary), "go") {\n-\t\tsummary = "Go " + summary\n-\t}\n-\n-\t// Open file to upload.\n-\tf, err := os.Open(filename)\n+\tsvc, err := storage.New(oauthClient)\n \tif err != nil {\n \t\treturn err\n \t}\n-\tdefer f.Close()\n \n-\t// Prepare multipart payload.\n-\tbody := new(bytes.Buffer)\n-\tw := multipart.NewWriter(body)\n-\tif err := w.WriteField("summary", summary); err != nil {\n-\t\treturn err\n-\t}\n-\tfor _, l := range labels {\n-\t\tif err := w.WriteField("label", l); err != nil {\n-\t\t\treturn err\n-\t\t}\n+\tobj := &storage.Object{\n+\t\tAcl: []*storage.ObjectAccessControl{{Entity: "allUsers", Role: "READER"}},\n+\t\tName: filename,\n \t}\n-\tfw, err := w.CreateFormFile("filename", filename)\n+\tf, err := os.Open(filename)\n \tif err != nil {\n \t\treturn err\n \t}\n-\tif _, err = io.Copy(fw, f); err != nil {\n-\t\treturn err\n-\t}\n-\tif err := w.Close(); err != nil {\n-\t\treturn err\n-\t}\n-\n-\t// Send the file to Google Code.\n-\treq, err := http.NewRequest("POST", uploadURL, body)\n+\tdefer f.Close()\n+\t_, err = svc.Objects.Insert(*storageBucket, obj).Media(f).Do()\n \tif err != nil {\n \t\treturn err\n \t}\n-\ttoken := fmt.Sprintf("%s:%s", username, password)\n-\ttoken = base64.StdEncoding.EncodeToString([]byte(token))\n-\treq.Header.Set("Authorization", "Basic "+token)\n-\treq.Header.Set("Content-type", w.FormDataContentType())\n \n-\tresp, err := http.DefaultTransport.RoundTrip(req)\n-\tif err != nil {\n-\t\treturn err\n-\t}\n-\tif resp.StatusCode/100 != 2 {\n-\t\tfmt.Fprintln(os.Stderr, "upload failed")\n-\t\tdefer resp.Body.Close()\n-\t\tio.Copy(os.Stderr, resp.Body)\n-\t\treturn fmt.Errorf("upload: %s", resp.Status)\n+\treturn nil\n+}\n+\n+func setupOAuthClient() error {\n+\tconfig := &oauth.Config{\n+\t\tClientId: "999119582588-h7kpj5pcm6d9solh5lgrbusmvvk4m9dn.apps.googleusercontent.com",\n+\t\tClientSecret: "8YLFgOhXIELWbO",\n+\t\tScope: storage.DevstorageRead_writeScope,\n+\t\tAuthURL: "https://accounts.google.com/o/oauth2/auth",\n+\t\tTokenURL: "https://accounts.google.com/o/oauth2/token",\n+\t\tTokenCache: oauth.CacheFile(*tokenCache),\n+\t\tRedirectURL: "oob",\n+\t}\n+\ttransport := &oauth.Transport{Config: config}\n+\tif token, err := config.TokenCache.Token(); err != nil {\n+\t\turl := transport.Config.AuthCodeURL("")\n+\t\tfmt.Println("Visit the following URL, obtain an authentication" +\n+\t\t\t"code, and enter it below.")\n+\t\tfmt.Println(url)\n+\t\tfmt.Print("Enter authentication code: ")\n+\t\tcode := ""\n+\t\tif _, err := fmt.Scan(&code); err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t\tif _, err := transport.Exchange(code); err != nil {\n+\t\t\treturn err\n+\t\t}\n+\t} else {\n+\t\ttransport.Token = token\n \t}\n+\toauthClient = transport.Client()\n \treturn nil\n }\n \n@@ -785,21 +732,11 @@ func readCredentials() error {\n \t\treturn err\n \t}\n \tdefer f.Close()\n-\tr := bufio.NewReader(f)\n-\tfor i := 0; i < 3; i++ {\n-\t\tb, _, err := r.ReadLine()\n-\t\tif err != nil {\n-\t\t\treturn err\n-\t\t}\n-\t\tb = bytes.TrimSpace(b)\n-\t\tswitch i {\n-\t\tcase 1:\n-\t\t\tusername = string(b)\n-\t\tcase 2:\n-\t\t\tpassword = string(b)\n-\t\t}\n+\ts := bufio.NewScanner(f)\n+\tif s.Scan() {\n+\t\tbuilderKey = s.Text()\n \t}\n-\treturn nil\n+\treturn s.Err()\n }\n \n func cp(dst, src string) error {\n```
## コアとなるコードの解説
### インポートの変更
* `- "encoding/base64"`: Basic認証のトークンをエンコードするために使用されていたため、不要になり削除されました。
* `- "mime/multipart"`: HTTPリクエストのボディをmultipart/form-data形式で構築するために使用されていましたが、GCSクライアントライブラリが内部で処理するため、不要になり削除されました。
* `+ "code.google.com/p/goauth2/oauth"`: OAuth2認証フローを扱うためのライブラリが追加されました。
* `+ "code.google.com/p/google-api-go-client/storage/v1beta2"`: Google Cloud Storage APIをGoから操作するためのクライアントライブラリが追加されました。
### グローバル変数とフラグの追加
* `tokenCache`: OAuth2トークンをキャッシュするファイルのパスを指定する新しいコマンドラインフラグが追加されました。これにより、認証情報を毎回入力する手間が省けます。
* `storageBucket`: アップロード先のGCSバケット名を指定する新しいコマンドラインフラグが追加されました(デフォルトは `golang`)。
* `defaultCacheFile`: トークンキャッシュファイルのデフォルトパス(ユーザーのホームディレクトリ下の `.makerelease-request-token`)が定義されました。
* `oauthClient *http.Client`: OAuth2認証済みのHTTPクライアントを保持するためのグローバル変数が追加されました。このクライアントは、GCS APIへのすべてのリクエストに使用されます。
* `builderKey string`: `readCredentials` 関数で読み込まれるビルダキーを保持するための変数が追加されました。以前の `username`, `password` 変数は削除されました。
### `main` 関数の変更
* `if *upload` ブロック内で、`readCredentials()` の呼び出しに加えて、新しく `setupOAuthClient()` が呼び出されるようになりました。これにより、ファイルアップロードの前にOAuth2認証が完了し、認証済みHTTPクライアントが準備されます。
* `log.Println` が `log.Fatalln` に変更され、認証情報の読み込みやOAuthクライアントのセットアップに失敗した場合にプログラムが即座に終了するようになりました。
### `Upload` 関数の大幅な変更
`Upload` 関数は、Google CodeへのアップロードロジックからGCSへのアップロードロジックへと完全に書き換えられました。
* **旧ロジックの削除**: ファイルのメタデータ(`summary`, `label`)を手動で構築し、`multipart/form-data` を使用してHTTPリクエストを送信する約120行のコードが削除されました。これには、Basic認証ヘッダーの生成も含まれていました。
* **GCSクライアントの初期化**:
```go
svc, err := storage.New(oauthClient)
if err != nil {
return err
}
```
`storage.New(oauthClient)` を呼び出すことで、OAuth2認証済みの `oauthClient` を使用してGoogle Cloud Storageサービスクライアントが初期化されます。これにより、GCS APIへのアクセスが可能になります。
* **オブジェクトメタデータの設定**:
```go
obj := &storage.Object{
Acl: []*storage.ObjectAccessControl{{Entity: "allUsers", Role: "READER"}},
Name: filename,
}
```
アップロードするGCSオブジェクトのメタデータが定義されます。
* `Acl`: アクセス制御リストを設定します。`Entity: "allUsers", Role: "READER"` は、アップロードされたオブジェクトが公開され、誰でも読み取れるように設定することを意味します。これは、Goのリリースバイナリが一般に公開される必要があるため重要です。
* `Name`: GCSバケット内でオブジェクトが保存される名前を、元のファイル名 (`filename`) と同じに設定します。
* **ファイルのアップロード実行**:
```go
f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()
_, err = svc.Objects.Insert(*storageBucket, obj).Media(f).Do()
if err != nil {
return err
}
return nil
```
`os.Open(filename)` でアップロード対象のファイルを読み込み用に開きます。
`svc.Objects.Insert(*storageBucket, obj).Media(f).Do()` がGCSへの実際のアップロードを実行します。`*storageBucket` はアップロード先のバケット、`obj` はオブジェクトメタデータ、`Media(f)` はアップロードするファイルのリーダーを指定します。`Do()` メソッドがAPIリクエストを送信します。
### `setupOAuthClient` 関数の追加
```go
func setupOAuthClient() error {
config := &oauth.Config{
ClientId: "999119582588-h7kpj5pcm6d9solh5lgrbusmvvk4m9dn.apps.googleusercontent.com",
ClientSecret: "8YLFgOhXIELWbO",
Scope: storage.DevstorageRead_writeScope,
AuthURL: "https://accounts.google.com/o/oauth2/auth",
TokenURL: "https://accounts.google.com/o/oauth2/token",
TokenCache: oauth.CacheFile(*tokenCache),
RedirectURL: "oob",
}
transport := &oauth.Transport{Config: config}
if token, err := config.TokenCache.Token(); err != nil {
url := transport.Config.AuthCodeURL("")
fmt.Println("Visit the following URL, obtain an authentication" +
"code, and enter it below.")
fmt.Println(url)
fmt.Print("Enter authentication code: ")
code := ""
if _, err := fmt.Scan(&code); err != nil {
return err
}
if _, err := transport.Exchange(code); err != nil {
return err
}
} else {
transport.Token = token
}
oauthClient = transport.Client()
return nil
}
この新しい関数は、OAuth2認証フローを処理します。
oauth.Config
を初期化し、Google API Consoleで取得したクライアントIDとシークレット、必要なスコープ(GCSへの読み書き権限)、Googleの認可エンドポイントとトークンエンドポイント、そしてトークンキャッシュファイルのパスを設定します。RedirectURL: "oob"
は、CLIアプリケーションで認証コードを手動で入力する「Out-of-Band」フローを示します。config.TokenCache.Token()
を試行し、キャッシュされたトークンが存在しないか期限切れの場合、ユーザーに認可URLへのアクセスを促し、認証コードを手動で入力させます。- 入力された認証コードを
transport.Exchange(code)
でアクセストークンとリフレッシュトークンに交換し、トークンキャッシュに保存します。 - キャッシュされたトークンが存在する場合は、それを使用して
transport.Token
を設定します。 - 最終的に、認証済みの
*http.Client
インスタンスをoauthClient
グローバル変数に割り当てます。
readCredentials
関数の変更
- 旧来の
username
とpassword
を読み込むロジックが削除され、代わりにbuilderKey
のみを読み込むように簡素化されました。これは、GCSへのアップロードにユーザー名とパスワードのBasic認証が不要になったためです。
これらの変更により、makerelease
ツールは、よりモダンで安全なGoogle Cloud StorageのAPIとOAuth2認証を利用して、Goのリリース成果物をアップロードできるようになりました。
関連リンク
- Google Cloud Storage 公式ドキュメント: https://cloud.google.com/storage/docs
- OAuth 2.0 公式仕様: https://oauth.net/2/
golang.org/x/oauth2
パッケージ (現在のGo OAuth2クライアントライブラリ): https://pkg.go.dev/golang.org/x/oauth2cloud.google.com/go/storage
パッケージ (現在のGo GCSクライアントライブラリ): https://pkg.go.dev/cloud.google.com/go/storage
参考にした情報源リンク
- Google Code Project Hosting 廃止に関する情報:
- Google Cloud Storage の概要:
- OAuth 2.0 のフローとGo言語での実装に関する情報:
code.google.com/p/goauth2
およびcode.google.com/p/google-api-go-client
の歴史的背景に関する情報 (Goの公式リポジトリの変更履歴など)- https://go.dev/doc/go1.4#goauth2 (Go 1.4 リリースノートで
goauth2
の移動について言及) - https://go.dev/doc/go1.5#google-api-go-client (Go 1.5 リリースノートで
google-api-go-client
の移動について言及)
- https://go.dev/doc/go1.4#goauth2 (Go 1.4 リリースノートで