l0w.dev

msgraph.go プログラミング入門


はじめに

この記事は Azure Advent Calendar 2019 の 10 日目の記事です。

msgraph.go を使った初歩的なプログラミングについて少し詳しく説明します。 msgraph.go には msgraph-me というサンプルプログラムがありますので、 これを題材にしていきたいと思います。

msgraph.go の紹介

msgraph.goGo 言語Microsoft Graph サービスを利用するためのパッケージを集めたモジュールです。

Microsoft Graph は Azure Active Directory や Office 365 などのリソースを操作するための共通の REST API エンドポイントを実現するもので、 Microsoft のクラウドサービス利用の根幹をなす重要なサービスです。

msgraph.go はそれに対する Go 言語のクライアントライブラリ、SDK ということになります。 興味がある方は Go Conference 2019 Autumn のセッション資料 Microsoft Graph API Library for Go をご覧ください。

また Microsoft が公式に用意している他の言語・処理系向けの SDK については Microsoft Graph SDKs - Requirements and Design を参照してください。

msgraph.go 開発環境の準備

msgraph.go を使った開発をするには Go 1.12 以降が必要ですので用意してください。

Go の開発環境がセットアップできていれば、 次のコマンドで msgraph-me をビルド・インストールすることができます。

$ go get github.com/yaegashi/msgraph.go/cmd/msgraph-me

msgraph.go では長大な名前を持つ型や関数が大量に定義されていますので、 強力な補完が利用できる Visual Studio Code などのエディタを利用することをおすすめします。

msgraph-me 動作の説明

msgraph-me は、Microsoft のクラウドサービスのユーザーのプロフィールを取得し、 ユーザーが所有する OneDrive 内のファイルをダウンロードする簡単な CLI プログラムです。

$ msgraph-me -h
Usage of msgraph-me:
  -client-id string
        Client ID (default "45c7f99c-0a94-42ff-a6d8-a8d657229e8c")
  -tenant-id string
        Tenant ID (default "common")
  -token-cache-path string
        Token cache path (default "token_cache.json")

msgraph-me を実行すると最初にユーザーの認証を要求してきます。 次のように Microsoft の認証サイトの URL https://microsoft.com/deviceloginHW338DXAK のような認証コードを表示しますので、 ブラウザで認証サイトを開いて認証コードをコピペで入力し、その後自分のアカウントでサインインしてください。 個人アカウント (Microsoft アカウント) と組織アカウント (Azure AD アカウント) の両方のユーザーに対応しています。

$ msgraph-me
To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code HW338DXAK to authenticate.

サインインすると msgraph-me に与えるアクセス権限について確認されます。 msgraph-me は Microsoft Graph を使ってサインインしたユーザーの情報を読み取りますが、変更は行いません。

アクセス許可を承認すると msgraph-me はカレントディレクトリの token_cache.json というファイルに認証情報を保存します。 次回以降の起動ではこのファイルを読み込んで使用しますので、再度サインインを求められることはありません。

最初にサインインしたユーザー自身の情報を取得してコンソール出力します。

2019/12/06 20:13:25 Get current logged in user information
2019/12/06 20:13:25 GET https://graph.microsoft.com/v1.0/me
{
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
  "id": "126871095554adb4",
  "displayName": "八重樫 剛史",
  "givenName": "剛史",
  "surname": "八重樫",
  "userPrincipalName": "yaegashi@live.jp"
}

次にユーザーのドライブのルートフォルダを検索して、ルートフォルダにあるすべてのファイルを表示します。 ドライブとは OneDrive (MSアカウント) や OneDrive for Business (組織アカウント) のことです。 もしユーザーにドライブのライセンスがない場合はここで終了します。

2019/12/06 20:13:25 Get files in the root folder of user's drive
2019/12/06 20:13:25 GET https://graph.microsoft.com/v1.0/me/drive/root/children
2019/12/06 20:13:25   DIR  2019-04-04T13:58:24Z          0 Attachments
2019/12/06 20:13:25   DIR  2018-06-16T08:21:45Z    5872012 ドキュメント
2019/12/06 20:13:25   DIR  2013-01-12T22:13:20Z          0 公開
2019/12/06 20:13:25   DIR  2016-03-01T17:13:19Z          0 画像
2019/12/06 20:13:25   DIR  2019-10-21T14:44:33Z          0 ノートブック
2019/12/06 20:13:25   FILE 2019-10-21T17:14:15Z       7990 ブック.xlsx
2019/12/06 20:13:25   FILE 2019-10-21T17:14:16Z      29313 プレゼンテーション.pptx
2019/12/06 20:13:25   FILE 2019-09-16T05:12:22Z      11393 文書.docx

その後入力待ちになりますので ENTER を押すとファイルのみをダウンロードして終了します。

Press ENTER to download files or Ctrl-C to abort: 
2019/12/06 20:13:27 Download "ブック.xlsx" (7990 bytes)
2019/12/06 20:13:28 Download "プレゼンテーション.pptx" (29313 bytes)
2019/12/06 20:13:28 Download "文書.docx" (11393 bytes)

msgraph-me プログラムの説明

ここからは msgraph-me プログラムのソースコード main.go を説明していきます。

msgraph パッケージのインポート

msgraph パッケージは次のようにインポートして使います。

import msgraph "github.com/yaegashi/msgraph.go/v1.0"

ベータ版の API エンドポイントにアクセスする場合は次のようにインポートします。

import msgraph "github.com/yaegashi/msgraph.go/beta"

クライアントの作成

最初に NewClient() で Microsoft Graph クライアントを作成する必要があります。

	ctx := context.Background()
	m := msauth.NewManager()
	m.LoadFile(tokenCachePath)
	ts, err := m.DeviceAuthorizationGrant(ctx, tenantID, clientID, defaultScopes, nil)
	if err != nil {
		log.Fatal(err)
	}
	m.SaveFile(tokenCachePath)

	httpClient := oauth2.NewClient(ctx, ts)
	graphClient := msgraph.NewClient(httpClient)

NewClient() にはリクエストを発行する http.Client のインスタンスを渡します。 Microsoft Graph では認証に OAuth2 を使いますので oauth2 などで作った http.Client を用意します。

msgraph.go にはおまけパッケージとしてとして Azure AD v2.0 エンドポイントから oauth2.TokenSource を取得するパッケージ msauth があります。 msgraph-me ではその DeviceAuthorizationGrant() 関数を使用してデバイスコード認証を行っています。 またリフレッシュトークンを含むトークン情報を token_cache.json ファイルに保存し、次回のアクセストークンの自動取得に利用しています。

ユーザー情報の取得

クライアントが作成できたら、それを使ってサインインしたユーザー自身 (me) の情報を取得し、コンソール出力します。

次のコードはリクエスト GET https://graph.microsoft.com/v1.0/me を発行し、 レスポンスの JSON オブジェクトを User として取得します。 dump() はそれを JSON にしてコンソール出力するデバッグ用のルーチンです。

	{
		log.Printf("Get current logged in user information")
		req := graphClient.Me().Request()
		log.Printf("GET %s", req.URL())
		user, err := req.Get(ctx)
		if err == nil {
			dump(user)
		} else {
			log.Println(err)
		}
	}

ドライブのファイルリストの取得

引き続き、ドライブのファイル情報を取得します。

次のコードはリクエスト GET https://graph.microsoft.com/v1.0/me/drive/root/children を発行しています。 レスポンスは DriveItem のコレクションで、スライスとして得られます。

	var items []msgraph.DriveItem
	{
		log.Printf("Get files in the root folder of user's drive")
		req := graphClient.Me().Drive().Root().Children().Request()
		// This filter is not supported by OneDrive for Business or SharePoint Online
		// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/concepts/filtering-results?#filterable-properties
		// req.Filter("file ne null")
		log.Printf("GET %s", req.URL())
		items, err = req.Get(ctx)
		if err != nil {
			log.Println(err)
		}
	}

コレクションの件数が多い場合に必要なページングリクエストは msgraph.go が内部で自動的に行ってくれます。

DriveItem とはファイルやフォルダのメタデータを表す構造体型です。 この構造体から必要な情報だけを取り出してコンソールに表示します。 構造体のフィールドはすべてポインタ型として定義されていることに注意してください。

	for _, item := range items {
		timestamp := item.LastModifiedDateTime.Format(time.RFC3339)
		itemType := "FILE"
		if item.File == nil {
			itemType = "DIR "
		}
		log.Printf("  %s %s %10d %s", itemType, timestamp, *item.Size, *item.Name)
	}

ファイルのダウンロード

最後に、各ファイルの中身をダウンロードします。

Microsoft Graph から返される DriveItem のうち、 中身がダウンロード可能なファイルには @microsoft.graph.downloadUrl という追加情報が付加されており、 この URL を GET することでファイル内容のダウンロードができます。 @microsoft.graph.downloadUrl のような、構造体のフィールドとして定義されていない任意の付加情報は GetAdditionalData() というメソッドで取得することができます。

	for _, item := range items {
		if item.File == nil {
			continue
		}
		err := func() error {
			log.Printf("Download %q (%d bytes)", *item.Name, *item.Size)
			if url, ok := item.GetAdditionalData("@microsoft.graph.downloadUrl"); ok {
				res, err := http.Get(url.(string))
				if err != nil {
					return err
				}
				defer res.Body.Close()
				if res.StatusCode != http.StatusOK {
					b, _ := ioutil.ReadAll(res.Body)
					return fmt.Errorf("%s: %s", res.Status, string(b))
				}
				f, err := os.Create(*item.Name)
				if err != nil {
					return err
				}
				defer f.Close()
				_, err = io.Copy(f, res.Body)
				if err != nil {
					return err
				}
			} else {
				return fmt.Errorf("unknown download URL")
			}
			return nil
		}()
		if err != nil {
			log.Println(err)
		}
	}

上記の処理は .NET 用 Microsoft Graph ライブラリのサンプル とほぼ同じことをしていますので、比較してみてください。

すべてのファイルのダウンロードが終わったら msgraph-me プログラムは終了です。

msgraph.go プログラミング Tips

ここまでの説明でご理解いただけたと思いますが、 msgraph.go を活用するにはまず Microsoft Graph の REST API についての理解が必要になります。 ドキュメントおよび API リファレンスは常に参照するようにしてください。

個々の API の動作確認には、次の Web アプリがとても役に立ちます。

自動生成された msgraph.go のパッケージのドキュメントは pkg.go.dev で参照できます。

なお、ほとんどの型や関数のドキュメントが残念ながら undocumented なままです。 また beta のドキュメントは大きすぎて pkg.go.dev では生成失敗するようです (pkg.go.dev の限界について)。 これらの問題はできるだけ早く改善していきたいと思います。

クライアントライブラリとしての msgraph.go は .NET 用クライアントライブラリGraphServiceClient とリクエストビルダ を真似ています。発行するべき HTTP リクエストがわかってさえいれば、 それを msgraph.go のコードに変換することは容易にできるようになっています。

リクエストビルダのメソッドチェインの記法は一見すると冗長ですが、エディタによる補完が活用できますので、 長大なメソッドチェインになっても楽に入力できるはずです。

まとめ

msgraph.go の基本的なサンプルプログラム msgraph-me の動作について説明しました。 msgraph.go プログラミングの参考になれば幸いです。

今後は、ユーザーやグループを登録したり、Excel ファイルの中身を操作したりといったような、 より複雑なプログラムについて記事にしていきたいと思います。