📘
AWS Lambda と rclone の活用術:OneDrive から S3 にファイルコピー
はじめに
阿河です。
ファイル移行は、様々なプロジェクトで必要となるユースケースです。
本記事では、AWS Lambdaを活用し、OneDrive からS3への安全なファイル移行をrcloneを使って実現する方法を解説します。
誰かの参考になれば幸いです。
目次
- 事前準備
- Lambdaの実装
- 動作確認
1. 事前準備
Secrets Managerの準備
今回の実装では、シークレット情報群をSecrets Managerに保存して管理します。
- Secret name: rclone_conf
- Onedriveのトークン/Drive IDの入力
- Onedriveのtypeは自身のドライブに合わせて入力(personal | business | documentLibrary)
- アクセスキー/シークレットキーに紐づく権限は、S3に対する最低限の権限に絞ります。
rcloneの準備
適当なS3バケットにrclone関連のファイルをアップします。
以下の手順で準備しました。
$ curl -O https://downloads.rclone.org/rclone-current-linux-amd64.zip
$ unzip rclone-current-linux-amd64.zip
$ cd rclone-*-linux-amd64
$ mkdir rclone_bin
$ cp rclone rclone_bin/
$ zip -r rclone_bin.zip rclone_bin
上記で作成したrclone_bin.zipを、S3バケットにアップロードしました。
2. Lambdaの実装
Lambda設定
Lambda Runtime: Python3.13
Architecture: x86_64
Lambda関数を作成したら、環境変数を設定します。
ONEDRIVE_PATH:
→OneDriveのファイルが保存してあるフォルダのパス
RCLONE_SECRET_ARN:
→シークレットのARN
S3_BUCKET_PATH:
→s3:[※バケット名]/[※プレフィックス]
その他、タイムアウトは30秒前後に設定。
実行ロールにはS3とSecrets Managerに対する権限を付与。
コード実装
以下はサンプルコードです。
必要に応じてコードを書き換えてください。
import os
import boto3
from botocore.exceptions import ClientError
import json
import subprocess
import zipfile
import sys
# rcloneのzipファイルをS3からダウンロードして展開
def download_and_extract_s3_zip(bucket_name, zip_key, extract_path):
s3_client = boto3.client('s3')
zip_path = '/tmp/temp_rclone.zip'
try:
# S3からファイルをダウンロードして、Lambdaの一時領域(/tmp/temp_rclone.zip)に保存
print(f"Downloading {zip_key} from S3 bucket {bucket_name} to {zip_path}")
s3_client.download_file(bucket_name, zip_key, zip_path)
# ダウンロード結果を確認(デバッグ用)
# ファイルサイズも載せる
if os.path.exists(zip_path):
file_size = os.path.getsize(zip_path)
print(f"DEBUG: File {zip_path} exists with size {file_size} bytes.")
else:
print(f"DEBUG: File {zip_path} does not exist after download.")
return
# zipファイルの解凍処理
print(f"Extracting {zip_path} to {extract_path}")
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
zip_ref.extractall(extract_path)
# 解凍後のファイルリストを表示
# DEBUG1: Extracted files: ['temp_rclone.zip', 'python']
# DEBUG1: Extracted files: ['bin', 'temp_rclone.zip', 'python']
extracted_files = os.listdir(extract_path)
print(f"DEBUG1: Extracted files: {extracted_files}")
except zipfile.BadZipFile:
print(f"ERROR: The file {zip_path} is not a valid zip file.")
raise
except Exception as e:
print(f"Error during extraction: {str(e)}")
raise
# Secrets Managerから認証情報取得
def get_secret(secret_name, region_name):
session = boto3.session.Session()
client = session.client(service_name='secretsmanager', region_name=region_name)
# rclone.confの内容を取得
try:
get_secret_value_response = client.get_secret_value(SecretId=secret_name)
except ClientError as e:
raise e
# 必要な値を取り出す
secret = get_secret_value_response['SecretString']
# Pythonで扱えるようにする
try:
return json.loads(secret)
except json.JSONDecodeError:
raise ValueError("The secret is not a valid JSON format.")
# rclone.confを動的に生成
def create_rclone_conf(secret_dict):
if not isinstance(secret_dict, dict):
raise ValueError(f"Expected dictionary, got {type(secret_dict)} instead.")
sections = {}
for key, value in secret_dict.items():
if "_" not in key:
section = "default"
field = key
else:
section, field = key.split("_", 1)
# セクション名を特別処理: "test-onedrive" を "onedrive" に変換
if section == "test-onedrive":
section = "onedrive"
elif section == "test-rclone":
section = "s3"
if section not in sections:
sections[section] = {}
# OneDrive の token を特別処理
if section == "onedrive" and field == "token":
try:
token_data = json.loads(value) # トークン情報を JSON として解釈
sections[section]["token"] = json.dumps(token_data) # 必要な形式に再エンコード
except json.JSONDecodeError:
raise ValueError(f"Invalid JSON format for token: {value}")
else:
sections[section][field] = value
# rclone.conf の生成
rclone_conf_content = ""
for section, fields in sections.items():
rclone_conf_content += f"[{section}]\n"
for field, value in fields.items():
if isinstance(value, str):
rclone_conf_content += f"{field} = {value}\n"
else:
rclone_conf_content += f"{field} = {json.dumps(value)}\n"
rclone_conf_content += "\n"
#print(f"DEBUG: Generated rclone.conf content:\n{rclone_conf_content}")
return rclone_conf_content
def lambda_handler(event, context):
# download_file APIを利用する際に指定するバケット内のキー
rclone_python_zip_key = "rclone_bin.zip"
try:
# S3からrclone_python.zipとrclone.zipをダウンロード
download_and_extract_s3_zip("xxxxxxx", rclone_python_zip_key, "/tmp")
# 解凍後の確認(デバッグ)
extracted_files = os.listdir("/tmp")
#print(f"DEBUG2: Files in /tmp: {extracted_files}")
# rcloneバイナリのフルパスを指定(デバッグ用)
rclone_executable = "/tmp/rclone_bin/rclone"
# バイナリが存在するか確認(デバッグ)
# DEBUG: rclone exists at /tmp/bin/rclone
if os.path.exists(rclone_executable):
print(f"DEBUG: rclone exists at {rclone_executable}")
else:
raise FileNotFoundError(f"rclone binary not found at {rclone_executable}")
# 実行権限を付与
os.chmod(rclone_executable, 0o755)
#print(f"DEBUG: rclone file permissions set to executable.")
# PATH環境変数を更新
os.environ["PATH"] += os.pathsep + "/tmp/rclone_bin"
#print(f"DEBUG: PATH updated: {os.environ['PATH']}")
# 環境変数の取得
secret_arn = os.environ["RCLONE_SECRET_ARN"]
region_name = os.environ["AWS_REGION"]
# Secrets Managerからシークレット情報を取得(rclone.confの情報)
secret_dict = get_secret(secret_arn, region_name)
#print(f"DEBUG: Retrieved secret: {secret_dict}")
# rclone.conf内の文字列生成
rclone_conf_content = create_rclone_conf(secret_dict)
# 一時ディレクトリ内にrclone.confを作成するファイルパスを指定
# ファイルへの書き込み
rclone_conf_path = '/tmp/rclone.conf'
with open(rclone_conf_path, 'w') as rclone_conf_file:
rclone_conf_file.write(rclone_conf_content)
# パスの指定
os.environ["RCLONE_CONFIG"] = rclone_conf_path
onedrive_path = os.environ.get("ONEDRIVE_PATH")
s3_bucket_path = os.environ.get("S3_BUCKET_PATH")
if not onedrive_path or not s3_bucket_path:
raise ValueError("ONEDRIVE_PATHまたはS3_BUCKET_PATHが設定されていません。")
#print("debug1")
# rcloneの実行
# 更新が必要なファイルだけコピー
result = subprocess.run(
["rclone", "copy", onedrive_path, s3_bucket_path, "--update"],
capture_output=True,
text=True
)
print("complete run")
# rcloneのログを出力
print(f"Rclone log: {result.stdout}")
print(f"Rclone error: {result.stderr}")
# rcloneが失敗した場合のエラーハンドリング
if result.returncode != 0:
raise RuntimeError(f"Rclone failed: {result.stderr}")
return {
'statusCode': 200,
'body': f"File copied successfully from {onedrive_path} to {s3_bucket_path}"
}
except Exception as e:
print(f"Error occurred: {str(e)}")
return {
'statusCode': 500,
'body': f"Exception occurred while copying file from {os.environ.get('ONEDRIVE_PATH', 'None')} to {os.environ.get('S3_BUCKET_PATH', 'None')}: {str(e)}"
}
【xxx】には、rcloneファイルをアップロードしたS3バケット名を指定してください。
③動作イメージ
2つのファイルをOneDriveに保存
- OneDrive側の操作
- Lambda実行
Response:
{
"statusCode": 200,
"body": "File copied successfully from onedrive:/test-folder to s3:xxx-test-rclone/test"
}
- S3バケット内を確認
- 2つのファイルがコピーされました。
OneDrive側に1つファイルを追加
新規ファイルをOnDriveにアップします。
- 新規ファイルぶんだけ反映がされます。
おわりに
今回の構成では、OneDrive からS3への安全なファイル転送をrcloneとAWS Lambdaを組み合わせて実現しました。
本記事が、セキュアなファイル転送の仕組みを構築する参考になれば幸いです。
今後も AWS の活用方法に関する記事を投稿予定です。ぜひご期待ください!
Discussion