はじめに

AWS S3 のオブジェクト群を Firebase の Storage (Cloud Storage for Firebase) にコピーする方法。

TL;DR

  • 公式手順(参考1, 参考2)があるので、要件を満たせるならそれを使う
  • 公式手順でダメな場合は自分でスクリプトを組む
  • 目的の 公式ドキュメント(API リファレンス)にたどり着く追うのが割と大変
この記事が参考になった方
ここここからチャージや購入してくれると嬉しいです(ブログ主へのプレゼントではなく、ご自身へのチャージ)
欲しいもの / Wish list

目次

  1. はじめに
  2. TL;DR
  3. 環境・条件
  4. 詳細
    1. 公式手順
    2. 今回やりたいこと
    3. セットアップ
      1. パッケージインストール
      2. Firebase Admin SDK 秘密鍵生成
      3. AWS IAM アクセスキー生成
    4. サンプルコード
      1. 簡単な解説
      2. 使用しているメソッドなど
  5. その他・メモ
    1. 苦労した点
  6. まとめ
  7. 参考文献

環境・条件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.15.7
BuildVersion: 19H2

$ node -v
v12.7.0

$ npm -v
6.14.5

$ npm ls aws-sdk firebase-admin
├── aws-sdk@2.772.0
└── firebase-admin@9.2.0

詳細

公式手順

「どこかのタイミングで1回コピー」や「毎日1回コピー」で良ければ公式手順がある。

以下も参考になるかも。

今回やりたいこと

今回やりたいことは以下。前述の公式手順では要件を満たせないため自分で Node.js でスクリプトを作成した。

  • S3 から Storage に定期的(1時間に1回など、1日より短いスパン)にコピー
  • コピー先の格納パスを変更したい(S3 で /a/b/c.png だったものを Storage では /x/y/c.png に置きたい)

セットアップ

パッケージインストール

npm iaws-sdk (aws/aws-sdk-js)firebase-admin (firebase/firebase-admin-node) をインストール。

1
$ npm i aws-sdk firebase-admin

Firebase Admin SDK 秘密鍵生成

Firebase web console → プロジェクト選択 → プロジェクトを設定 → サービスアカウント → Firebase Admin SDK → 新しい秘密鍵の生成

ダウンロードしたファイルを、スクリプトと同じディレクトリに配置。(xxxx-adminsdk-xxxx.json)

AWS IAM アクセスキー生成

AWS Console → IAM → ユーザー → 認証情報 → アクセスキーの生成


CSV ファイルをダウンロードしたら、スクリプトと同じディレクトリに JSON ファイルを作成。(今回は aws.config.json とした)

1
2
3
4
5
{
"accessKeyId": "YourAccessKeyId",
"secretAccessKey": "YourSecretAccessKey",
"region": "ap-northeast-1"
}

サンプルコード

S3 から 1ファイル取得して、Storage にアップロードするサンプル。例外処理とかちゃんとやってないので注意。

複数ファイルを処理したい場合は、listObjects と組み合わせればいけるはず。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
const AWS = require('aws-sdk');
const path = require('path');
const fs = require('fs');

// https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/loading-node-credentials-json-file.html
AWS.config.loadFromPath('./aws.config.json');

// https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/s3-example-creating-buckets.html
const s3 = new AWS.S3({apiVersion: '2006-03-01'});

// Firebase Admin 初期化
const firebaseAdmin = require('firebase-admin');
const serviceAccount = require("./xxxx-adminsdk-xxxx.json");
// https://firebase.google.com/docs/reference/admin/node/admin#initializeapp
// https://firebase.google.com/docs/reference/admin/node/admin.AppOptions
firebaseAdmin.initializeApp({
credential: firebaseAdmin.credential.cert(serviceAccount),
storageBucket: "gs://xxxx.appspot.com",
});


// Bucket 一覧
// (検証用に作った、今回は未使用)
const listBuckets = () => {
return new Promise((resolve, reject) => {
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#listBuckets-property
s3.listBuckets((err, data) => {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
};


// Object 一覧(最大1000件)
// (検証用に作った、今回は未使用)
const listObjects = (params) => {
return new Promise((resolve, reject) => {
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#listObjects-property
s3.listObjects(params, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
};


// Object 取得
const getObject = (params) => {
return new Promise((resolve, reject) => {
// https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getObject-property
s3.getObject(params, function(err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
});
};


const main = async () => {
let res;

// Bucket 一覧
// res = await listBuckets();
// console.log({ buckets: res.Buckets });

let params = {};

// Object 一覧
// params = {
// Bucket: 'your-s3-bucket-name',
// };
// res = await listObjects(params);
// console.log({ objects: res.Contents });

// Object 取得 & 保存
params = {
Bucket: 'your-s3-bucket-name',
Key: "your/aws/s3/object.ext",
};
res = await getObject(params);

// Buffer でそのまま upload できないのでいったん保存
// https://nodejs.org/api/fs.html#fs_fs_mkdirsync_path_options
// https://nodejs.org/api/path.html#path_path_dirname_path
fs.mkdirSync(path.dirname(params.Key), { recursive: true });
// https://nodejs.org/api/fs.html#fs_fs_writefilesync_file_data_options
fs.writeFileSync(params.Key, res.Body, 'binary');

// Storage for firebase にアップロード
// https://firebase.google.com/docs/reference/admin/node/admin.storage.Storage#bucket
const firebaseBucket = firebaseAdmin.storage().bucket();
// destination でアップロード先を指定できるので、必要に応じて変更する
// https://googleapis.dev/nodejs/storage/latest/Bucket.html#upload
firebaseBucket.upload(params.Key, { destination: params.Key })
.then(r => console.log({r}))
.catch(e => console.error({e}));
}

main();

簡単な解説

コード読めばわかるけどやってることは以下。

  • AWS S3 を扱うための設定
  • Firebase Admin(Storage for Firebase) を扱うための設定
  • S3 からオブジェクト取得
  • いったんローカルに保存
  • 保存したファイルを Storage にアップロード

もう少しちゃんとやるとしたら、以下あたりの処理追加や考慮が必要。

  • listObjects で複数ファイルのパスを取得して処理
    • MaxKeysMarker オプションを使ったページング処理が必要
  • Storage にアップロード成功したローカルファイルの削除
  • Storage にアップロード失敗したファイルの情報保持
  • 処理が途中で落ちた場合に再開できるような情報(例: アップロード成功した最新ファイル名など)の保持
  • 毎回 await すると効率が悪い(と思う)ので、非同期で処理
    • listObjects で100件取ってきたら、100件分は一気にダウンロード(getObject)して、すべてアップロードできたら次100件にいく、とか
  • 例外処理全般

使用しているメソッドなど

コード中にもコメントでリンクを書いているが簡単に整理(一部は API doc にたどり着くのに苦労したので。。。

その他・メモ

苦労した点

AWS も Firebase も公式ガイドがある、のは良いんだけど突っ込んだ事をやろうと思った時に、情報の繋がりがなかったり firebase-admin ではなく firebase (firebase/firebase-js-sdk) の例だったりで調べるのに苦労した。


ウェブでファイルをアップロードする | Firebase にアップロード方法が書かれているが、前述の通り firebase のサンプルなので Node.js 環境だと使えなかったりする。

また、Storage | Admin Node.js SDK | Firebase をちゃんと見るとわかるが、Bucket 取得したらその後は firebase-admin ではなく @google-cloud/storage (googleapis/nodejs-storage) 側のドキュメントを見る必要があったりする。(なので、Admin Node.js SDK 側でアップロードするメソッドを探しても見つからない)

まとめ

  • 公式手順(参考1, 参考2)があるので、要件を満たせるならそれを使う
  • 公式手順でダメな場合は自分でスクリプトを組む
  • 目的の 公式ドキュメント(API リファレンス)にたどり着く追うのが割と大変

参考文献

関連記事

この記事が参考になった方
ここここからチャージや購入してくれると嬉しいです(ブログ主へのプレゼントではなく、ご自身へのチャージ)
欲しいもの / Wish list