@@ -10,10 +10,49 @@ const cognitoIdp = new AWS.CognitoIdentityServiceProvider({ apiVersion: '2016-04
1010const s3 = new AWS . S3 ( { apiVersion : '2006-03-01' } )
1111const dynamodb = new AWS . DynamoDB ( { apiVersion : '2012-08-10' } )
1212
13+ // Using newline-delimited JSON to reduce memory requirements in case the user list is
14+ // sufficiently large.
15+ async function uploadJsonStream ( { event, file, hwm } , init ) {
16+ console . log ( `Creating stream to s3://${ event . Bucket } /${ file } ` )
17+ const uploadStream = new PassThrough ( { writableHighWaterMark : hwm } )
18+ const uploadPromise = s3 . upload ( {
19+ Bucket : event . Bucket ,
20+ Key : file ,
21+ Body : uploadStream
22+ } ) . promise ( )
23+
24+ try {
25+ await init ( item => {
26+ uploadStream . write ( JSON . stringify ( item ) + '\n' )
27+ } )
28+ } finally {
29+ uploadStream . end ( )
30+ console . log ( `Closing stream for s3://${ event . Bucket } /${ file } ` )
31+ await uploadPromise
32+ }
33+ }
34+
35+ function createBackup ( event , file , sourceTable ) {
36+ return uploadJsonStream ( { event, file, hwm : 16 /* MB */ * 1024 /* KB */ * 1024 /* B */ } , async write => {
37+ const options = { TableName : sourceTable }
38+
39+ do {
40+ console . log ( 'Reading users' )
41+ const resp = await dynamodb . scan ( options ) . promise ( )
42+
43+ options . ExclusiveStartKey = resp . LastEvaluatedKey
44+
45+ for ( const item of resp . Items ) write ( item )
46+ } while ( options . ExclusiveStartKey != null )
47+ } )
48+ }
49+
1350exports . handler = async event => {
1451 let userPoolId , adminsGroup , registeredGroup
1552 let customersTableName , feedbackTableName
1653
54+ console . log ( 'Reading stack resources' )
55+
1756 {
1857 const options = { StackName : event . StackName }
1958
@@ -51,47 +90,40 @@ exports.handler = async event => {
5190 } while ( options . NextToken != null )
5291 }
5392
54- await dynamodb . createBackup ( {
55- TableName : customersTableName ,
56- BackupName : event . BackupPrefix + '-Customers'
57- } )
93+ const promises = [ ]
5894
59- await dynamodb . createBackup ( {
60- TableName : feedbackTableName ,
61- BackupName : event . BackupPrefix + '-Feedback'
62- } )
95+ console . log ( 'Creating customers table backup' )
96+ promises . push ( createBackup ( event , 'dev-portal-migrate/customers.ndjson' , customersTableName ) )
6397
64- return new Promise ( ( resolve , reject ) => {
65- // Using newline-delimited JSON to reduce memory requirements in case the user list is
66- // sufficiently large.
67- const uploadStream = new PassThrough ( {
68- // Set a much higher high water mark than the default - this is only used once, and the
69- // pipeline won't wait for it to buffer. Intermediate values shouldn't come anywhere close
70- // to this.
71- writableHighWaterMark : 64 /* MB */ * 1024 /* KB */ * 1024 /* B */
72- } )
73- const uploadPromise = s3 . upload ( {
74- Bucket : event . Bucket ,
75- Key : 'dev-portal-migrate.ndjson' ,
76- Body : uploadStream
77- } ) . promise ( )
98+ if ( feedbackTableName ) {
99+ promises . push ( createBackup ( event , 'dev-portal-migrate/feedback.ndjson' , feedbackTableName ) )
100+ } else {
101+ console . log ( 'No feedback table found, skipping backup' )
102+ }
78103
79- let open = 1
104+ console . log ( 'Creating user pool backup' )
80105
81- function start ( ) {
82- open ++
83- }
106+ promises . push ( uploadJsonStream ( {
107+ event,
108+ file : 'dev-portal-migrate/users.ndjson' ,
109+ // Set a much higher high water mark than the default - this is only used once, and the
110+ // pipeline won't wait for it to buffer. Intermediate values shouldn't come anywhere close
111+ // to this.
112+ hwm : 64 /* MB */ * 1024 /* KB */ * 1024 /* B */
113+ } , write => new Promise ( ( resolve , reject ) => {
114+ let open = 1
84115
85116 function pass ( ) {
86117 if ( open > 0 && -- open === 0 ) {
87- uploadStream . end ( )
88- resolve ( uploadPromise )
118+ console . log ( 'Users backed up, ending upload stream and returning' )
119+ resolve ( )
89120 }
90121 }
91122
92123 function fail ( e ) {
93124 if ( open > 0 ) {
94125 open = 0
126+ console . error ( 'An error occurred' , e )
95127 reject ( e )
96128 }
97129 }
@@ -105,12 +137,14 @@ exports.handler = async event => {
105137 // any sort of results.
106138
107139 async function processUser ( user ) {
108- start ( )
140+ open ++
141+ console . log ( `Backing up user: ${ user . Username } ` )
109142
110143 const options = { UserPoolId : userPoolId , Username : user . Username }
111144 let isAdmin = false
112145 let isRegistered = false
113146
147+ console . log ( `Enumerating groups for user: ${ user . Username } ` )
114148 do {
115149 const resp = await cognitoIdp . adminListGroupsForUser ( options ) . promise ( )
116150 options . NextToken = resp . NextToken
@@ -124,6 +158,7 @@ exports.handler = async event => {
124158 }
125159 } while ( options . NextToken != null )
126160
161+ console . log ( `Serializing attributes for user: ${ user . Username } ` )
127162 // Only serialize what's needed, to save space and speed up restoration.
128163 // (Restoration is more network-intensive than backup.)
129164 const attributes = Object . create ( null )
@@ -132,18 +167,20 @@ exports.handler = async event => {
132167 attributes [ attr . Name ] = attr . Value
133168 }
134169
170+ attributes . email = user . Username
135171 attributes . _isAdmin = isAdmin
136172 attributes . _isRegistered = isRegistered
137173
138- uploadStream . write ( JSON . stringify ( attributes ) + '\n' , 'utf-8' )
174+ console . log ( `Writing user: ${ user . Username } ` )
175+ write ( attributes )
139176 }
140177
141- start ( )
142-
178+ open ++
143179 ; ( async ( ) => {
144180 const userOptions = { UserPoolId : userPoolId }
145181
146182 do {
183+ console . log ( 'Reading users' )
147184 const { Users, PaginationToken } = await cognitoIdp . listUsers ( userOptions ) . promise ( )
148185
149186 userOptions . PaginationToken = PaginationToken
@@ -154,5 +191,8 @@ exports.handler = async event => {
154191 }
155192 } while ( userOptions . PaginationToken != null )
156193 } ) ( ) . then ( pass , fail )
157- } )
194+ pass ( )
195+ } ) ) )
196+
197+ await Promise . all ( promises )
158198}
0 commit comments