0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

GoogleDrive から GoogleCloudStorageへ移動ハンズオン

Posted at

はじめに

仕事の都合で GoogleCloudを触り始めた、GoogleCloud歴 3か月の駆け出しエンジニアです。

GoogleDriveから、GoogleCloudStorage(以下、GCS)に資材を移動させようとした際、これといったマネージドサービスが見つかりませんでしたので、「GoogleDrive API」を利用して資材移動に挑戦してみました。

方法論

  • Google Drive から GCSへの資材移動
    image.png

ハンズオン

1.Google Cloudの準備

1.1.プロジェクト作成・API有効化

  • 以下ブログを参考にGoogleCloudの設定をする。

参照ブログ:AWS Lambda から Gemini APIを利用した呼出しハンズオン

  • 「1.3.API有効化」では、以下「Google Drive API」を有効化する。
    image.png

1.2.サービスアカウントの作成

  • 「IAMと管理」から左ペイン「サービスアカウント」へ移動して「+ サービスアカウントを作成」を押下する。
    image.png

  • サービスアカウント作成

設定内容
サービスアカウント名 ${サービスアカウント名} drive-gcs-transfer
サービスアカウントID ${サービスアカウントID} drive-gcs-transfer
サービスアカウント説明 ${サービスアカウント説明} 空欄
ロールを選択 ${必要ロール} ・Storage オブジェクト作成者
  • 作成完了後、タブ「鍵」を押下して「キーを追加」押下して、サービスアカウントのキーを「JSON形式」でダウンロードする(後述手順 4.1.事前準備の「ファイルのアップロード」で利用)
    image.png

2.GCSの作成

  • 「マネジメントコンソール」画面から「GCS」画面へ遷移します。
  • 「バケットを作成」を選択し、新規バケットを作成する。
設定内容
バケット名 ${バケット名} drive-gcs-transfer

3.GoogleDriveの準備

  • 「1.2.サービスアカウントの作成」で作成したサービスアカウントに「閲覧者」としての権限を付与する。
    image.png

  • GCSへ転送させるフォルダID(赤枠部分)を取得して、メモ等に保存する。
    image.png

4.Cloud Shellの準備

4.1.事前準備

  • マネジメントコンソール右上 [>.] アイコンより、Cloudshellの起動し以下コマンドを実施する。

  • Cloud Shellでディレクトリ作成

mkdir drive-gcs-transfer
cd drive-gcs-transfer
  • Python 仮想環境作成
python3 -m venv venv
  • 仮想環境の有効化
source venv/bin/activate
  • requirements.txt ファイルの作成
google-api-python-client
google-auth-httplib2
  • 依存ライブラリのインストール
pip install -r requirements.txt
  • ファイルのアップロード(1.2.サービスアカウントの作成で取得した「JSON」ファイルをアップロード)
    image.png

  • 設定ファイルの作成

内容
Servece_Account_Key 「1.2.サービスアカウントの作成」で取得したJSONファイル名
DRIVE_FOLDER_ID 「3.GoogleDriveの準備」で取得した ファイルID
GCS_BUCKET_NAME 「2.GCSの作成」で作成したGCSバケット名
GCS_DESTINATION_FOLDER GCS内の作成したいフォルダ名
vi config.ini
cat config.ini

[DEFAULT]
KEY_PATH=()drive-gcs-transfer-1234567-890.json
DRIVE_FOLDER_ID=()123456789-d
GCS_BUCKET_NAME=()drive-gcs-transfer
GCS_DESTINATION_FOLDER=()destination_folder

4.2.スクリプト

import os
import subprocess
import time
from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.http import MediaIoBaseDownload
import configparser

# 設定ファイルを読み込む
config = configparser.ConfigParser()
config.read('config.ini')

# 設定ファイルから設定値を読み込む
KEY_PATH = config['DEFAULT']['KEY_PATH'].strip("'")
DRIVE_FOLDER_ID = config['DEFAULT']['DRIVE_FOLDER_ID'].strip("'")
GCS_BUCKET_NAME = config['DEFAULT']['GCS_BUCKET_NAME'].strip("'")
GCS_DESTINATION_FOLDER = config['DEFAULT']['GCS_DESTINATION_FOLDER'].strip("'")


def download_from_drive(file_id, filepath):
    """Google Drive からファイルをダウンロードします."""
    creds = service_account.Credentials.from_service_account_file(KEY_PATH)
    drive_service = build('drive', 'v3', credentials=creds)

    request = drive_service.files().get_media(fileId=file_id)
    with open(filepath, 'wb') as fh:
        downloader = MediaIoBaseDownload(fh, request)
        done = False
        while done is False:
            status, done = downloader.next_chunk()
            print(f"Download Progress: {int(status.progress() * 100)}%")

def upload_to_gcs(filepath, bucket_name, upload_path):
    """GCS バケットへファイルをアップロードします."""
    gcs_path = f"gs://{bucket_name}/{upload_path}"
    command_str = f"gsutil cp {filepath} {gcs_path}"
    subprocess.run(command_str, shell=True, check=True)
    print(f"File uploaded to {gcs_path}")

def create_gcs_folder(bucket_name, folder_path):
    """GCS にフォルダを作成する (擬似フォルダを作る)"""
    gcs_folder_path = f"gs://{bucket_name}/{folder_path}/"
    empty_file = "/dev/null"

    command = ['gsutil', 'cp', empty_file, gcs_folder_path]
    try:
        result = subprocess.run(command, check=True, stderr=subprocess.PIPE, text=True)
        print(f"フォルダ (擬似フォルダ) 作成成功: {gcs_folder_path}")
    except subprocess.CalledProcessError as e:
        print(f"エラーが発生しました:{e}")
        print(f"エラー出力:{e.stderr}")

def delete_null_file_in_gcs_folder(bucket_name, folder_path):
    """GCS フォルダ (擬似フォルダ) 内の null ファイルを削除します."""
    gcs_null_file_path = f"gs://{bucket_name}/{folder_path}/null"
    command = ['gsutil', 'rm', gcs_null_file_path]
    try:
        result = subprocess.run(command, check=True, stderr=subprocess.PIPE, text=True)
        print(f"null ファイル削除成功: {gcs_null_file_path}")
    except subprocess.CalledProcessError as e:
        print(f"エラーが発生しました:{e}")
        print(f"エラー出力:{e.stderr}")


def get_drive_folder_name(folder_id):
    """Google Drive フォルダの名前を取得します."""
    creds = service_account.Credentials.from_service_account_file(KEY_PATH)
    drive_service = build('drive', 'v3', credentials=creds)
    try:
        folder = drive_service.files().get(fileId=folder_id, fields='name').execute()
        return folder.get('name')
    except Exception as error:
        print(f'An error occurred: {error}')
        return None


def list_drive_files_in_folder(folder_id):
    """Google Drive フォルダ内のファイルとフォルダをリストアップします."""
    creds = service_account.Credentials.from_service_account_file(KEY_PATH)
    drive_service = build('drive', 'v3', credentials=creds)
    results = []
    page_token = None
    while True:
        try:
            param = {}
            param['q'] = f"'{folder_id}' in parents and trashed=false"
            param['pageToken'] = page_token if page_token else None
            file_list = drive_service.files().list(**param).execute()

            results.extend(file_list.get('files', []))
            page_token = file_list.get('nextPageToken')
            if not page_token:
                break
        except Exception as error:
            print(f'An error occurred: {error}')
            break
    return results


def transfer_drive_folder_to_gcs(drive_folder_id, gcs_bucket_name, gcs_destination_folder):
    """Google Drive フォルダを GCS に転送します."""

    drive_folder_name = get_drive_folder_name(drive_folder_id) # Google Drive フォルダ名を取得
    if not drive_folder_name:
        print("Google Drive フォルダ名の取得に失敗しました。")
        return

    gcs_parent_folder_path = os.path.join(gcs_destination_folder, drive_folder_name) # GCS 親フォルダパスを構築
    create_gcs_folder(gcs_bucket_name, gcs_parent_folder_path) # GCS 親フォルダを作成

    file_list = list_drive_files_in_folder(drive_folder_id)

    for item in file_list:
        item_name = item['name']
        item_id = item['id']
        item_mime_type = item['mimeType']

        gcs_item_path = os.path.join(gcs_parent_folder_path, item_name) # GCS でのパスを親フォルダ配下に構築

        if item_mime_type == 'application/vnd.google-apps.folder':
            # フォルダの場合、GCS にフォルダを作成し、再帰的に処理 (必要に応じて実装)
            print(f"Creating folder: {item_name}")
            create_gcs_folder(gcs_bucket_name, gcs_item_path)
            print(f"Processing folder: {item_name}")
            transfer_drive_folder_to_gcs(item_id, gcs_bucket_name, gcs_item_path)

        else:
            # ファイルの場合、ダウンロードして GCS にアップロード
            local_filepath = os.path.join('/tmp', item_name)
            print(f"Downloading file: {item_name}")
            download_from_drive(item_id, local_filepath)
            print(f"Uploading file: {item_name} to gs://{gcs_bucket_name}/{gcs_item_path}")
            upload_to_gcs(local_filepath, gcs_bucket_name, gcs_item_path)
            os.remove(local_filepath)
            print(f"Temporary file {local_filepath} deleted")
        time.sleep(1)  # API Rate Limit 対策 (必要に応じて調整)

    delete_null_file_in_gcs_folder(gcs_bucket_name, gcs_parent_folder_path) # null ファイルを削除


if __name__ == '__main__':
    transfer_drive_folder_to_gcs(DRIVE_FOLDER_ID, GCS_BUCKET_NAME, GCS_DESTINATION_FOLDER) # GCS 転送先フォルダを事前に作成
    print("Folder transfer completed.")

5.実行結果

5.1.コンソールでの見え方

(venv) $ python drive_to_gcs_folder.py 
# フォルダ作成の通知
フォルダ (擬似フォルダ) 作成成功: 
gs://drive-gcs-transfer/destination_folder/GCS転送用フォルダ/

# GoogleDriveからのダウンロード
Downloading file: test01.xlsx
Download Progress: 100%

# GCSへアップロード
Uploading file: test01.xlsx to gs://drive-gcs-transfer/destination_folder/GCS転送用フォルダ/test02.xlsx
Copying file:///tmp/test01.xlsx [Content-Type=application/vnd.openxmlformats-officedocument.spreadsheetml.sheet]...
/ [1 files][  6.4 KiB/  6.4 KiB] Operation completed over 1 objects/6.4 KiB. File uploaded to gs://drive-gcs-transfer/destination_folder/GCS転送用フォルダ/test01.xlsx

# ローカルのファイル削除
Temporary file /tmp/test01.xlsx deleted

# フォルダ作成時に作成された nullファイルの削除
null ファイル削除成功: gs://drive-gcs-transfer/destination_folder/GCS転送用フォルダ/null
Folder transfer completed.

5.2.GCSのフォルダ構成

  • 「Google Drive」のフォルダ名および、フォルダ内のオブジェクトが、「GCS」に移動していることが画面からもわかります。
    image.png

おわりに

得られた知見

  • GoogleDriveAPIの使い方
  • GoogleCloudにおけるサービスアカウントへの理解

今後の課題

  • 生成AIで利用する元ネタを集めるための構築でしたので、これからGoogleDriveAPIを利用してGCSへ保管していく予定ですが、上限値などより細かい設定に関して理解を深めたいです。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?