見出し画像

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への認証が一番面倒でした!

この記事が気に入ったらサポートをしてみませんか?