Google DriveからAzure Blob Storageにファイルをコピーしてみた
Google Drive上にあるファイルをAzure Blob Strorageのコンテナに移動したいという要求があったのでいっちょやってみました。
1. 概要
ファイルをダウンロードしてからアップロードするのはファイルの削除とか面倒だし、ダウンロードとアップロードで二度手間だし、なんかイヤだなと思ったので、Streamを直接接続してしまえばよいのでは?と考えやってみました。需要?そういうのは気にしない。
2. 動作環境
Node.js: 18.12.1
※Azure Functionで実行しています
3. 事前準備
1. Google側の準備
Google Driveにフォルダを作成します
フォルダにコピーしたいファイルを入れておきます
フォルダIDを控えておきます
IDはURLの末尾の値になります
https://drive.google.com/drive/u/0/folders/フォルダID
共有ドライブを使用する場合はドライブのIDも控えておく必要があります
共有ドライブのIDはルートフォルダのIDとなります
Google Cloudにプロジェクトを作成します
Drive APIを有効にします
サービスアカウントを作成します
認証用のキーを作成し、JSON形式で取得します
サービスアカウントを該当フォルダ(共有ドライブの場合はドライブ)の共有ユーザーに追加します
※Google Cloudのプロジェクトの作成については今回は詳細な手順は省略します。
2. Azure側の準備
ストレージアカウントを作成します
3. node_modulesのインストール
次のモジュールを使用しました
@azure/identity: 4.0.0
@azure/storage-blob: 12.17.0
googleapis: 120.0.0
4. Google Driveへのアクセス
1. Googleの認証とDriveへのアクセス
const { google } = require('googleapis');
// google認証のクライアントを生成する
const auth = new google.auth.GoogleAuth({
credentials: 'サービスアカウント作成時に取得した認証キーのJSON文字列',
scopes: ['https://www.googleapis.com/auth/drive.readonly'],
});
const client = await auth.getClient();
google.options({ auth: client });
// Google Drive を取得する
const drive = google.drive('v3');
サービスアカウント作成時に取得した認証キーをGoogle Apiの auth.GoogleAuthに渡すことで認証を行います。
認証キーはGoogleAuthのオプションに設定します。
2. 認証オプション
credentials
認証キーを指定します。今回はサービスアカウントの認証キーにあるJSON文字列を使用します。
scoprs
アクセス範囲を指定します。今回はファイルの読み込みだけなのでreadonlyを指定しています。
その他のスコープについて
認証にはJSONファイルを指定することもできます。
その場合オプションには credentialsではなく、keyFileを使用します。
const auth = new googleapis.auth.GoogleAuth({
keyFile: "JSONファイルのパス"
});
こんな感じです。
5. Google Driveからファイルを取得する
ファイルを取得するのにはファイルのIDが必要になります。ファイルIDを取得するにはフォルダ内のファイル情報一覧を取得し、ファイルの情報を取り出す必要があります。ファイル情報の取得にはfiles.listメソッドを使用します。
file.list APIについて 公式リファレンス
1. 検索用のパラメーターを作成する
まずはfiles.listメソッドに渡す検索用のパラメーターを作成します。検索結果に対して様々な指定を行うことが出来ます。
今回は次のようなパラメーターを作成しました。
const folderIds = ['ファイル一覧を取得したいフォルダID', ...];
let query = folderIds.map(folderId => `'${folderId}' in parents).join(' or ');
query += ' and trush = false';
let param = {
fields: 'nextPageToken, files(id, name, modifiedTime)',
pageSize: '10',
q: query,
}
2. 検索用パラメーターについて
fields(公式リファレンス)
レスポンスで返却されるフィールドを指定します。
nextPageToken
pageSize以上の項目が存在する場合、続きを取得する際に必要になるトークンを取得します
files
取得したいファイルのメタ情報を指定します
この例ではファイルID、ファイル名、ファイル更新日時を指定しています
指定可能なメタ情報
pageSize
一度に取得出来る件数を指定します
q
取得結果のフィルタリングをするためのクエリです
この例では "folderId in parent" を指定することでフォルダID配下のアイテムを検索対象と指定しています。また、複数のフォルダを指定するので or で結合しています
trush = false を指定することでゴミ箱内のファイルは検索対象外としています
3. 共有ドライブを検索する場合
この検索パラメーターは認証を通したアカウントが持つマイドライブに対して有効です。Google Workspace上で使用できる共有ドライブに対して検索を実行したい場合は次のパラメーターとetなります。
let param = {
driveId: process.env.GOOGLE_DRIVE_TARGET_DRIVEID,
corpora: 'drive',
q: query,
pageSize: pageSize,
fields: 'nextPageToken, files(id, name, modifiedTime)',
includeItemsFromAllDrives: true,
supportsAllDrives: true,
}
4. 共有ドライブの検索に必要なパラメーターについて
driveId
共有ドライブのIDです
corpora
クエリが適用されるアイテムの範囲をしていします
共有ドライブを指定する場合、 driveを指定する必要があります
drive以外にはuser, domain, allDriveがあります
user
ログインしているユーザーのマイドライブが範囲となります
domain
ユーザーが所属しているドメインが共有している範囲になります
allDrive
ログインしているユーザーがアクセス可能なすべてのドライブが範囲になります
includeItemsFromAllDrives
検索結果に共有ドライブも含めるかどうかを指定します
supportsAllDrives
APIを実行するアプリが共有ドライブにもアクセス出来るかを指定します
5. APIを実行してファイル情報を取得する
files.list を実行してファイル情報を取得します。
今回の例では fields に nextPageToken を指定していますので、レスポンスにnextPageTokenが含まれます。
フォルダ内のファイルがpageSizeで指定した上限より多い場合、APIのレスポンス内のnextPageTokenに値が格納されます。逆にすべて取得出来た場合はnextPageTokenは含まれません。
nextPageTokenを条件にループさせることでファイル情報を全て取得することが出来ます。
let param = { 検索用パラメーター }; // 検索用パラメーター
let fileList = []; // ファイル情報の格納用List
let nextPageToken; // 検索続行用トークン
do {
if (nextPageToken) {
// 検索用トークンがあればパラメーターに追加する
param.pageToken = nextPageToken;
}
// Google Drive APIを実行する
const fileList = await drive.files.list(param);
// 結果から検索用トークンを取り出す
nextPageToken = fileList.data.nextPageToken ?? '';
// ファイル情報を格納する
fileList.data.files.map(file => (
// レスポンスに含まれるファイル情報をmapにする
{ id: file.id, name: file.name, updated: file.modifiedTime }
)).forEach(file => {
// ファイルを格納する
fileList.push(file);
});
} while(nextPageToken); // nextPageTokenが空になるまで繰り返すことで全件取得出来る
6. Azure Blob Storageへのアクセス
ファイルのコピー先であるAzure Blob Storageにアクセスします。
BlobStorageClientの作成時に指定する接続文字列は、Azure Portal→該当のストレージアカウント→セキュリティとネットワーク→アクセスキーから取得できます。
const { BlobServiceClient } = require("@azure/storage-blob");
// BlobStorageクライアントを作成する
const blobServiceClient = BlobServerClient.fromConnectionString(
'Azure Blob Storageの接続文字列'
);
// 格納先のコンテナクライアントを取得する
const containerClient = blobServiceClient.getContainerClient('コンテナ名');
7. クラウド間でデータをコピーする
あとはデータをコピーするだけです。
まずはGoogleDriveからファイルをストリームで取得します。
AzureのconteinerClientからBlockBlobClientを取得します。この際に設定するのは作成するBlob名です。GoogleDriveから取得したファイル名を設定しました。
ストリームをBlockBlobClientのuploadStreamにセットする事でコピー開始です。
fileList.map(fileInfo => {
const stream = await drive.files.get(
{ fileId: fileInfo.id, alt: 'media' },
{ responseType: 'stream' }
);
const blockBlobClient = containerClient.getBlockBlobClient(fileInfo.name);
await blockBlobClient.uploadStream(stream);
}
8. 感想
いろいろ調査することはあったのですが、実装してみたら思ったより簡単に出来ました。
async、awaitの処理は使用する環境に合わせて設定してください。
あんまり関係ないんですが、node.jsに向き合ったのは今回が初めてでいろいろ勉強になりました。
ちなみに、Google Driveへの認証が一番面倒でした!