theBenForce/dynamo-tools

View on GitHub
src/commands/copy.ts

Summary

Maintainability
B
6 hrs
Test Coverage
import { Command, flags } from "@oclif/command";
import * as AWS from "aws-sdk";
import CliProgress from "cli-progress";
import { DYNAMO_CLIENT_VERSION } from "../constants";

export default class Copy extends Command {
  static description = "Copy the contents of one dynamodb table to another";

  static flags = {
    help: flags.help({ char: "h" }),
    // flag with a value (-n, --name=VALUE)
    batchSize: flags.integer({ description: "The number of items to copy at a time", default: 25 }),
    region: flags.string({ description: "Region for both source and destination tables" }),
    sourceRegion: flags.string({ description: "Region of source table" }),
    sourceProfile: flags.string({ description: "AWS Credentials profile to copy from" }),
    destinationRegion: flags.string({ description: "Region of destination table" }),
    destinationProfile: flags.string({ description: "AWS Credentials profile to copy to" }),
    source: flags.string({ description: "Source table name", required: true }),
    destination: flags.string({ description: "Destination table name", required: true }),
  };

  async run() {
    const { flags } = this.parse(Copy);

    if (!flags.destinationRegion) {
      flags.destinationRegion = flags.region;
    }

    if (!flags.sourceRegion) {
      flags.sourceRegion = flags.region;
    }

    if (!flags.sourceRegion || !flags.destinationRegion) {
      this.error(`You must specific both a source and destination region`, { exit: 1 });
    }

    const dynamo = new AWS.DynamoDB({
      region: flags.sourceRegion,
      apiVersion: DYNAMO_CLIENT_VERSION,
    });

    const sourceDynamo = new AWS.DynamoDB.DocumentClient({
      region: flags.sourceRegion,
      apiVersion: DYNAMO_CLIENT_VERSION,
      credentials: flags.sourceProfile
        ? new AWS.SharedIniFileCredentials({ profile: flags.sourceProfile })
        : undefined,
    });
    const destDynamo = new AWS.DynamoDB.DocumentClient({
      region: flags.destinationRegion,
      apiVersion: DYNAMO_CLIENT_VERSION,
      credentials: flags.destinationProfile
        ? new AWS.SharedIniFileCredentials({ profile: flags.destinationProfile })
        : undefined,
    });

    const scanArgs = {
      TableName: flags.source,
      Limit: flags.batchSize,
    } as AWS.DynamoDB.DocumentClient.ScanInput;
    let results: AWS.DynamoDB.DocumentClient.ScanOutput;

    const batchWrite = {
      RequestItems: {},
    } as AWS.DynamoDB.DocumentClient.BatchWriteItemInput;

    const downloadBar = new CliProgress.SingleBar(
      { clearOnComplete: true },
      CliProgress.Presets.shades_classic
    );

    const tableInfo = await dynamo.describeTable({ TableName: flags.source }).promise();
    const approxTotal = tableInfo.Table?.ItemCount ?? 0;
    downloadBar.start(approxTotal, 0);
    let current = 0;

    do {
      results = await sourceDynamo.scan(scanArgs).promise();

      current += results.Count ?? 0;
      downloadBar.update(current);

      scanArgs.ExclusiveStartKey = results.LastEvaluatedKey;

      batchWrite.RequestItems[flags.destination] =
        results?.Items?.map((Item) => ({
          PutRequest: {
            Item,
          },
        })) ?? [];

      await destDynamo.batchWrite(batchWrite).promise();
    } while (results?.LastEvaluatedKey);

    downloadBar.stop();
    console.info(`Copied ${current} items from ${flags.source} to ${flags.destination}`);
  }
}