-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0f5a331
commit 19efb4b
Showing
6 changed files
with
195 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
import { CloudFormationCustomResourceEvent } from "aws-lambda"; | ||
import { Construct } from "constructs"; | ||
import { $AWS, Function } from "functionless"; | ||
|
||
export type CustomResourceMigrationsRunnerProps = { | ||
migrationFiles: string[]; | ||
}; | ||
|
||
export default class CustomResourceMigrationsRunner extends Construct { | ||
constructor( | ||
scope: Construct, | ||
id: string, | ||
props: CustomResourceMigrationsRunnerProps | ||
) { | ||
super(scope, id); | ||
|
||
new Function( | ||
scope, | ||
`${id}-MigrationsRunner`, | ||
async (event: CloudFormationCustomResourceEvent) => { | ||
console.log(event); | ||
|
||
const migrations = await $AWS.DynamoDB.Scan({ | ||
Table: migrationsHistoryTable, | ||
}); | ||
|
||
console.log({ migrations }); | ||
|
||
const migrationsToRun = props.migrationFiles.filter( | ||
(migrationFile) => | ||
!(migrations.Items ?? []).find( | ||
(migration) => migration.id.S === migrationFile | ||
) | ||
); | ||
|
||
console.log({ migrationsToRun }); | ||
|
||
// todo: Start the migrations | ||
} | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,29 +1,39 @@ | ||
import { Table as cdkTable } from "aws-cdk-lib/aws-dynamodb"; | ||
import { Construct } from "constructs"; | ||
import { $AWS } from "functionless"; | ||
import { ScanOutput } from "typesafe-dynamodb/lib/scan"; | ||
import { Migration, MigrationProps } from "../../app"; | ||
import { $AWS, Table } from "functionless"; | ||
import { unmarshall, marshall } from "typesafe-dynamodb/lib/marshall"; | ||
import { MigrationProps, Migration } from "../../migration"; | ||
|
||
export type MigrationFunction = ( | ||
scope: Construct, | ||
id: string, | ||
props: MigrationProps | ||
) => Migration<any>; | ||
|
||
export const up: MigrationFunction = (scope, migrationName) => | ||
// Initialize Migration Stack | ||
new Migration(scope, migrationName, { | ||
tableArn: "arn:aws:dynamodb:us-east-1:123456789012:table/SubjectTable", | ||
}).run(async (_table, result: ScanOutput<any, any, any>) => { | ||
// Actual migration code goes here. | ||
// Do something with each item in the table. | ||
const tableArn = | ||
"arn:aws:dynamodb:us-east-1:085108115628:table/TestStack-TableCD117FA1-ZVV3ZWUOWPO"; | ||
|
||
export const migration: MigrationFunction = (scope, migrationName) => { | ||
const migrationDefinition = new Migration<any>(scope, migrationName, { | ||
tableArn, | ||
migrationName, | ||
}); | ||
|
||
const table = Table.fromTable( | ||
cdkTable.fromTableArn(scope, "SubjectTable", tableArn) | ||
); | ||
|
||
// Actual migration code goes here. | ||
// For each item in the table | ||
migrationDefinition.scan(async ({ result }) => { | ||
for (const i of result.Items as any[]) { | ||
// Do the following: | ||
await $AWS.DynamoDB.PutItem({ | ||
Table: _table, | ||
Item: { | ||
id: { | ||
S: `${i}_migrated`, | ||
}, | ||
}, | ||
Table: table, | ||
Item: marshall({ ...unmarshall(i), migratedAt: Date.now() }), | ||
}); | ||
} | ||
}); | ||
|
||
return migrationDefinition; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from "./migrations-manager"; | ||
export * from "./migration"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { NestedStack } from "aws-cdk-lib"; | ||
import { Table as cdkTable } from "aws-cdk-lib/aws-dynamodb"; | ||
import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs"; | ||
import { LogLevel } from "aws-cdk-lib/aws-stepfunctions"; | ||
import { Construct } from "constructs"; | ||
import { | ||
ITable, | ||
StepFunction, | ||
Table, | ||
Function, | ||
$AWS, | ||
$SFN, | ||
} from "functionless"; | ||
import { ScanOutput } from "typesafe-dynamodb/lib/scan"; | ||
|
||
export type ScanTableOptions = { | ||
segments: number; | ||
}; | ||
|
||
export type MigrationProps = { | ||
tableArn: string; | ||
migrationName: string; | ||
}; | ||
|
||
export type TransformFunctionType<T extends object> = (input: { | ||
result: ScanOutput<T, any, any>; | ||
}) => Promise<any>; | ||
|
||
export class Migration<T extends object> extends NestedStack { | ||
public readonly table: ITable<T, any, any>; | ||
|
||
public readonly migrationName: string; | ||
|
||
constructor(scope: Construct, id: string, props: MigrationProps) { | ||
super(scope, id); | ||
|
||
this.migrationName = props.migrationName; | ||
this.table = Table.fromTable( | ||
cdkTable.fromTableArn(this, "SubjectTable", props.tableArn) | ||
); | ||
} | ||
|
||
// Creates a state machine scanning whole table in parallel and applying transform function to each item. | ||
public scan( | ||
_transformFn: TransformFunctionType<T>, | ||
options?: ScanTableOptions | ||
) { | ||
const totalSegments = options?.segments ?? 10; | ||
const segments = Array.from({ length: totalSegments }, (_, i) => i); | ||
|
||
// "this" cannot be referenced in a Function. | ||
const table = this.table; | ||
|
||
const transformFunction = new Function( | ||
this, | ||
"MigrationCallbackFunction", | ||
_transformFn | ||
); | ||
|
||
return new StepFunction( | ||
this, | ||
"MigrationStateMachine", | ||
{ | ||
stateMachineName: this.migrationName, | ||
logs: { | ||
destination: new LogGroup(this, "MigrationLogGroup", { | ||
retention: RetentionDays.ONE_WEEK, | ||
}), | ||
level: LogLevel.ALL, | ||
}, | ||
}, | ||
async () => { | ||
return $SFN.map(segments, async (_, index) => { | ||
let lastEvaluatedKey; | ||
let firstRun = true; | ||
|
||
while (firstRun || lastEvaluatedKey) { | ||
firstRun = false; | ||
|
||
const result = await $AWS.DynamoDB.Scan({ | ||
Table: table, | ||
TotalSegments: totalSegments, | ||
Segment: index, | ||
}); | ||
|
||
if (result.LastEvaluatedKey) { | ||
lastEvaluatedKey = result.LastEvaluatedKey; | ||
} | ||
|
||
await transformFunction({ result }); | ||
} | ||
}); | ||
} | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters