Skip to content

Commit 3dc3e0d

Browse files
author
amazon-meaisiah
committed
Fix several bugs, add lots of debugging logs
1 parent 10e7a9b commit 3dc3e0d

File tree

3 files changed

+243
-195
lines changed

3 files changed

+243
-195
lines changed

cloudformation/template.yaml

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2010,31 +2010,52 @@ Resources:
20102010
- logs:CreateLogStream
20112011
- logs:PutLogEvents
20122012
Resource: arn:aws:logs:*:*:*
2013+
- PolicyName: PassRole
2014+
PolicyDocument:
2015+
Version: '2012-10-17'
2016+
Statement:
2017+
- Effect: Allow
2018+
Action:
2019+
- iam:PassRole
2020+
Resource: !GetAtt UserGroupImporterLogsRole.Arn
20132021
- PolicyName: S3GetObject
20142022
PolicyDocument:
20152023
Version: '2012-10-17'
20162024
Statement:
20172025
- Effect: Allow
20182026
Action: s3:getObject
20192027
Resource: arn:aws:s3:::*/*
2028+
- PolicyName: RestoreTables
2029+
PolicyDocument:
2030+
Version: '2012-10-17'
2031+
Statement:
2032+
- Effect: Allow
2033+
Action:
2034+
- dynamodb:Scan
2035+
Resource: !If [
2036+
EnableFeedbackSubmission,
2037+
[
2038+
!GetAtt CustomersTable.Arn,
2039+
!GetAtt FeedbackTable.Arn,
2040+
],
2041+
[
2042+
!GetAtt CustomersTable.Arn,
2043+
],
2044+
]
20202045
- PolicyName: UpdateCognitoUserList
20212046
PolicyDocument:
20222047
Version: '2012-10-17'
20232048
Statement:
20242049
- Effect: Allow
20252050
Action:
2026-
- cognito-idp:GetCSVHeader
2027-
- cognito-idp:CreateUserImportJob
2028-
- cognito-idp:StartUserImportJob
2029-
- cognito-idp:DescribeUserImportJob
2030-
- cognito-idp:AdminAddUserToGroup
2051+
- cognito-idp:AdminCreateUser
20312052
- cognito-idp:AdminAddUserToGroup
20322053
Resource: !GetAtt CognitoUserPool.Arn
20332054

20342055
UserGroupImporter:
20352056
Type: AWS::Serverless::Function
20362057
Properties:
2037-
CodeUri: ../lambdas/dump-v3-account-data
2058+
CodeUri: ../lambdas/user-group-importer
20382059
Handler: index.handler
20392060
MemorySize: 512
20402061
Role: !GetAtt UserGroupImporterExecutionRole.Arn
@@ -2044,9 +2065,11 @@ Resources:
20442065
Environment:
20452066
Variables:
20462067
UserPoolId: !Ref CognitoUserPool
2047-
LogsRoleArn: !Ref UserGroupImporterLogsRole
2068+
LogsRoleArn: !GetAtt UserGroupImporterLogsRole.Arn
20482069
AdminsGroup: !Ref CognitoAdminsGroup
20492070
RegisteredGroup: !Ref CognitoRegisteredGroup
2071+
CustomersTable: !Ref CustomersTable
2072+
FeedbackTable: !If [EnableFeedbackSubmission, !Ref FeedbackTable, !Ref AWS::NoValue]
20502073

20512074

20522075
Outputs:

lambdas/user-group-exporter-v3/index.js

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,49 @@ const cognitoIdp = new AWS.CognitoIdentityServiceProvider({ apiVersion: '2016-04
1010
const s3 = new AWS.S3({ apiVersion: '2006-03-01' })
1111
const 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+
1350
exports.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

Comments
 (0)