目的
AWS S3 Glacier内の各ボールドのボールドインベントリをMongoDBに登録する。
やり方
- InitiateJobCommandを実行しボールドインベントリを要求する
- DescribeJobCommandを実行し進捗状況を取得する
- ボールドインベントリの取得が可能となったら、GetJobOutputCommandを実行しボールドインベントリを取得する
事前準備
ボールドインベントリの取得可能となったことを通知するために、ボールドを作成したリージョンと同一リージョンにて、Amazon SNSのトピックとサブスクリプションを作成する。
トピックの作成
- タイプ: スタンダード
- 名前: gtool-sns
サブスクリプションの作成
上記で作成したgtool-snsトピックを開き、サブスクリプションを作成する。
- トピックARN: arn:aws:sns:<リージョンID>:<アカウントID>:gtool-sns
- プロトコル: Eメール
- エンドポイント: 通知を受け取るメールアドレス
ソースコード
バックエンド
InitiateJobCommandを実行するinitiateInventoryJob API
src/app/api/initiateInventoryJob/route.ts
import { NextRequest, NextResponse } from "next/server";
import { GlacierClient, InitiateJobCommand } from "@aws-sdk/client-glacier";
import { MongoClient } from "mongodb";
export const corsHeaders = {
"Access-Control-Allow-Origin": "http://localhost:3000",
"Access-Control-Allow-Methods": "POST, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type",
};
export async function POST(request: NextRequest): Promise<NextResponse> {
const params = await request.json();
const vaultName = params["vaultName"];
const client = new GlacierClient({});
let input: {
accountId: string;
jobParameters: { Format: string; SNSTopic: string; Type: string };
vaultName: string;
} = {
accountId: "",
jobParameters: {
Format: "",
SNSTopic: "",
Type: "",
},
vaultName: "",
};
input.accountId = "-";
input.jobParameters.Format = "JSON";
input["jobParameters"]["SNSTopic"] =
"arn:aws:sns:<リージョンID>:<アカウントID>:gtool-sns";
input["jobParameters"]["Type"] = "inventory-retrieval";
if (vaultName) {
input["vaultName"] = vaultName;
}
const command = new InitiateJobCommand(input);
let response = await client.send(command);
const dbclient = await MongoClient.connect(
"mongodb://127.0.0.1/27017/glacier"
);
const db = dbclient.db("glacier");
const userVaults = db.collection("userInventoryJobs");
userVaults.insertOne({ info: response, vaultName: vaultName });
return NextResponse.json({}, { status: 200, headers: corsHeaders });
}
export async function OPTIONS() {
return NextResponse.json({}, { headers: corsHeaders });
}
DescribeJobCommandを実行するdescribeInventoryJob API
src/app/api/describeInventoryJob/route.ts
import { NextRequest, NextResponse } from "next/server";
import { GlacierClient, DescribeJobCommand } from "@aws-sdk/client-glacier";
import { MongoClient } from "mongodb";
export const corsHeaders = {
"Access-Control-Allow-Origin": "http://localhost:3000",
"Access-Control-Allow-Methods": "POST",
"Access-Control-Allow-Headers": "Content-Type",
};
export async function POST(request: NextRequest): Promise<NextResponse> {
const client = new GlacierClient({});
let input: {
accountId: string;
jobId: string;
vaultName: string;
} = {
accountId: "",
jobId: "",
vaultName: "",
};
const dbclient = await MongoClient.connect(
"mongodb://127.0.0.1/27017/glacier"
);
const db = dbclient.db("glacier");
const userArchiveJobs = db.collection("userInventoryJobs");
const cursor = userArchiveJobs.find();
for await (const doc of cursor) {
input.accountId = "-";
input["jobId"] = doc.info.jobId;
input["vaultName"] = doc.vaultName;
const command = new DescribeJobCommand(input);
let response = await client.send(command);
const userVaults = db.collection("userInventoryJobs");
const query = { "info.jobId": response.JobId };
const update = {
$set: {
info: {
Action: response.Action,
Completed: response.Completed,
CreationDate: response.CreationDate,
InventoryRetrievalParameters: response.InventoryRetrievalParameters,
jobId: response.JobId,
SNSTopic: response.SNSTopic,
StatusCode: response.StatusCode,
VaultARN: response.VaultARN,
},
},
};
userVaults.updateOne(query, update);
}
return NextResponse.json({}, { status: 200, headers: corsHeaders });
}
GetJobOutputCommandを実行するgetInventoryJobOutput API
src/app/api/getInventoryJobOutput/route.ts
import { NextRequest, NextResponse } from "next/server";
import { GlacierClient, GetJobOutputCommand } from "@aws-sdk/client-glacier";
import { MongoClient } from "mongodb";
export const corsHeaders = {
"Access-Control-Allow-Origin": "http://localhost:3000",
"Access-Control-Allow-Methods": "POST",
"Access-Control-Allow-Headers": "Content-Type",
};
export async function POST(request: NextRequest): Promise<NextResponse> {
const client = new GlacierClient({});
let input: {
accountId: string;
jobId: string;
range: string;
vaultName: string;
} = {
accountId: "",
jobId: "",
range: "",
vaultName: "",
};
const dbclient = await MongoClient.connect(
"mongodb://127.0.0.1/27017/glacier"
);
const db = dbclient.db("glacier");
const userArchiveJobs = db.collection("userInventoryJobs");
const cursor = userArchiveJobs.find();
for await (const doc of cursor) {
input.accountId = "-";
input.jobId = doc.info.jobId;
input["vaultName"] = doc.vaultName;
const command = new GetJobOutputCommand(input);
let response = await client.send(command);
let data;
if (response.body) {
data = JSON.parse(await response.body.transformToString("utf-8"));
} else {
console.error(`No body in response for job ${input.jobId}`);
return NextResponse.json({}, { status: 400, headers: corsHeaders });
}
const userVaults = db.collection("userVaults");
const query = { VaultARN: data.VaultARN };
const update: any = {
$set: {
VaultARN: data.VaultARN,
InventoryDate: data.InventoryDate,
ArchiveList: data.ArchiveList,
},
};
const options = { upsert: true };
userVaults.updateOne(query, update, options);
}
return NextResponse.json({}, { status: 200, headers: corsHeaders });
}
フロントエンド
src/app/page.tsx
"use client";
import axios from "axios";
- import { Button, Stack, Paper } from "@mui/material";
+ import { TextField, Button, Stack, Paper } from "@mui/material";
+ import { useForm, SubmitHandler } from "react-hook-form";
+ type Inputs = {
+ inventoryVaultName: string;
+ };
export default function Home() {
+ const { register, handleSubmit } = useForm<Inputs>({
+ defaultValues: { inventoryVaultName: "" },
+ });
const listVaults = async () => {
await axios.post("http://localhost:4000/api/listVaults");
};
+ const initiateInventoryJob: SubmitHandler<Inputs> = async (data) => {
+ await axios.post("http://localhost:4000/api/initiateInventoryJob", {
+ vaultName: data.inventoryVaultName,
+ });
+ };
+ const describeInventoryJob = async () => {
+ await axios.post("http://localhost:4000/api/describeInventoryJob");
+ };
+ const getInventoryJobOutput = async () => {
+ await axios.post("http://localhost:4000/api/getInventoryJobOutput");
+ };
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
<div className="z-10 max-w-10xl w-full items-center justify-between font-mono text-sm lg:flex">
<Stack spacing={2}>
<Paper>
<Button
variant="outlined"
onClick={listVaults}
style={{
textTransform: "none",
}}
>
ボールド一覧を登録する
</Button>
</Paper>
+ <Paper>
+ <TextField
+ {...register("inventoryVaultName")}
+ label="ボールド名"
+ variant="outlined"
+ />
+ <Button
+ variant="outlined"
+ onClick={handleSubmit(initiateInventoryJob)}
+ style={{
+ textTransform: "none",
+ }}
+ >
+ ボールドインベントリを要求する
+ </Button>
+ <Button
+ variant="outlined"
+ onClick={describeInventoryJob}
+ style={{
+ textTransform: "none",
+ }}
+ >
+ 進捗状況を確認する
+ </Button>
+ <Button
+ variant="outlined"
+ onClick={getInventoryJobOutput}
+ style={{
+ textTransform: "none",
+ }}
+ >
+ ボールドインベントリを登録する
+ </Button>
+ </Paper>
</Stack>
</div>
</main>
);
}
実行結果
フロントエンド
ブラウザを開き、http://localhost:3000
にアクセスする。
ボールドインベントリの要求
ボールドインベントリを要求するボールド名を入力後、[ボールドインベントリを要求する]ボタンを押すと、ボールドインベントリを取得するジョブの情報がMongoDBに登録される。
進捗状況の取得
[進捗状況を確認する]ボタンを押すと、ボールドインベントリを取得するジョブの情報に進捗情報が追加される。
ボールドインベントリの登録
ボールドインベントリの要求後数時間待つと、ボールドインベントリ取得の準備が整ったことを通知するメールが送信される。メールを受信後、[ボールドインベントリを登録する]ボタンを押して数分待つと、ボールド一覧のボールド情報に、ボールドインベントリが追加される。
(ボールドインベントリのダウンロードと情報の反映のため数分待つ必要がある。進捗状況を示す等のフロントエンドの改善は今後検討する。)
その4に続く。