Skip to content

Commit 5e81421

Browse files
authored
Wait for Lambda status change on create/update calls (#439)
Using Lambda States to confirm deployment/update status before allowing additional operations ahead of AWS-wide migration: https://aws.amazon.com/blogs/compute/coming-soon-expansion-of-aws-lambda-states-to-all-functions/
1 parent bae6b76 commit 5e81421

File tree

4 files changed

+123
-6
lines changed

4 files changed

+123
-6
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Feature",
3+
"description": "Lambda create and update tasks utilize Lambda function states to signal completion"
4+
}

src/tasks/LambdaDeployFunction/TaskOperations.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { SdkUtils } from 'lib/sdkutils'
1010
import { readFileSync } from 'fs'
1111
import { deployCodeAndConfig, deployCodeOnly, TaskParameters, updateFromLocalFile } from './TaskParameters'
1212

13+
const FUNCTION_UPDATED = 'functionUpdated'
14+
const FUNCTION_ACTIVE = 'functionActive'
15+
1316
export class TaskOperations {
1417
public constructor(
1518
public readonly iamClient: IAM,
@@ -66,11 +69,12 @@ export class TaskOperations {
6669
}
6770

6871
const response = await this.lambdaClient.updateFunctionCode(updateCodeRequest).promise()
69-
7072
if (!response.FunctionArn) {
7173
throw new Error(tl.loc('NoFunctionArnReturned'))
7274
}
7375

76+
await this.waitForUpdate()
77+
7478
return response.FunctionArn
7579
} catch (err) {
7680
throw new Error(`Error while updating function code: ${err}`)
@@ -124,6 +128,8 @@ export class TaskOperations {
124128
throw new Error(tl.loc('NoFunctionArnReturned'))
125129
}
126130

131+
await this.waitForUpdate()
132+
127133
// Update tags if we have them
128134
const tags = SdkUtils.getTagsDictonary<Lambda.Tags>(this.taskParameters.tags)
129135
if (tags && Object.keys(tags).length > 0) {
@@ -203,6 +209,12 @@ export class TaskOperations {
203209
throw new Error(tl.loc('NoFunctionArnReturned'))
204210
}
205211

212+
console.log(tl.loc('AwaitingStatus', this.taskParameters.functionName, FUNCTION_ACTIVE))
213+
await this.lambdaClient
214+
.waitFor(FUNCTION_ACTIVE, { FunctionName: this.taskParameters.functionName })
215+
.promise()
216+
console.log(tl.loc('AwaitingStatusComplete', this.taskParameters.functionName, FUNCTION_ACTIVE))
217+
206218
return response.FunctionArn
207219
} catch (err) {
208220
throw new Error(`Failed to create function, error ${err}`)
@@ -211,7 +223,7 @@ export class TaskOperations {
211223

212224
private async testFunctionExists(functionName: string): Promise<boolean> {
213225
try {
214-
const response = await this.lambdaClient
226+
await this.lambdaClient
215227
.getFunction({
216228
FunctionName: functionName
217229
})
@@ -222,4 +234,10 @@ export class TaskOperations {
222234
return false
223235
}
224236
}
237+
238+
private async waitForUpdate(): Promise<void> {
239+
console.log(tl.loc('AwaitingStatus', this.taskParameters.functionName, FUNCTION_UPDATED))
240+
await this.lambdaClient.waitFor(FUNCTION_UPDATED, { FunctionName: this.taskParameters.functionName }).promise()
241+
console.log(tl.loc('AwaitingStatusComplete', this.taskParameters.functionName, FUNCTION_UPDATED))
242+
}
225243
}

src/tasks/LambdaDeployFunction/task.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,9 @@
315315
"FunctionNotFound": "Function %s does not exist, cannot update code only",
316316
"TaskCompleted": "Completed create or update of Lambda function %s, function ARN %s",
317317
"AddingTag": "Adding tag. Key '%s', Value '%s'",
318-
"NoFunctionArnReturned": "No function ARN returned from the service! Deployment failed!"
318+
"NoFunctionArnReturned": "No function ARN returned from the service! Deployment failed!",
319+
"SettingOutputVariable": "Setting output variable %s with the function output",
320+
"AwaitingStatus": "Waiting for function %s to reach %s state...",
321+
"AwaitingStatusComplete": "Function %s has reached %s state"
319322
}
320323
}

tests/taskTests/lambdaDeployFunction/lambdaDeployFunction-test.ts

Lines changed: 95 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,18 @@ const getIamRoleSucceeds = {
7272
}
7373
}
7474

75+
const waitForSucceeds = {
76+
promise: function() {
77+
return {}
78+
}
79+
}
80+
81+
const waitForFails = {
82+
promise: function() {
83+
throw new Error('waited too long')
84+
}
85+
}
86+
7587
describe('Lambda Deploy Function', () => {
7688
// TODO https://github.com/aws/aws-vsts-tools/issues/167
7789
beforeAll(() => {
@@ -120,47 +132,124 @@ describe('Lambda Deploy Function', () => {
120132
})
121133

122134
test('Deploy only Function exists calls update', async () => {
123-
expect.assertions(2)
135+
expect.assertions(3)
124136
const taskParameters = { ...baseTaskParameters }
125137
taskParameters.deploymentMode = deployCodeOnly
126138
taskParameters.roleARN = 'arn:yes'
127139
const lambda = new Lambda() as any
128140
lambda.getFunction = jest.fn(() => getFunctionSucceeds)
129141
lambda.updateFunctionCode = jest.fn(() => updateFunctionSucceeds)
142+
lambda.waitFor = jest.fn(() => waitForSucceeds)
130143
const taskOperations = new TaskOperations(new IAM(), lambda, taskParameters)
131144
await taskOperations.execute()
132145
expect(lambda.getFunction).toBeCalledTimes(1)
133146
expect(lambda.updateFunctionCode).toBeCalledTimes(1)
147+
expect(lambda.waitFor).toBeCalledTimes(1)
148+
})
149+
150+
test('Deploy only Function exists calls update but fails if status does not update', async () => {
151+
expect.assertions(4)
152+
const taskParameters = { ...baseTaskParameters }
153+
taskParameters.deploymentMode = deployCodeOnly
154+
taskParameters.roleARN = 'arn:yes'
155+
const lambda = new Lambda() as any
156+
lambda.getFunction = jest.fn(() => getFunctionSucceeds)
157+
lambda.updateFunctionCode = jest.fn(() => updateFunctionSucceeds)
158+
lambda.waitFor = jest.fn(() => waitForFails)
159+
const taskOperations = new TaskOperations(new IAM(), lambda, taskParameters)
160+
await taskOperations.execute().catch(e => expect(`${e}`).toContain('waited too long'))
161+
expect(lambda.getFunction).toBeCalledTimes(1)
162+
expect(lambda.updateFunctionCode).toBeCalledTimes(1)
163+
expect(lambda.waitFor).toBeCalledTimes(1)
134164
})
135165

136166
test('Deploy and config does not exist calls create', async () => {
137-
expect.assertions(2)
167+
expect.assertions(3)
138168
const taskParameters = { ...baseTaskParameters }
139169
taskParameters.deploymentMode = deployCodeAndConfig
140170
taskParameters.roleARN = 'arn:yes'
141171
const lambda = new Lambda() as any
142172
lambda.getFunction = jest.fn(() => getFunctionFails)
143173
lambda.createFunction = jest.fn(() => updateFunctionSucceeds)
174+
lambda.waitFor = jest.fn(() => waitForSucceeds)
144175
const taskOperations = new TaskOperations(new IAM(), lambda, taskParameters)
145176
await taskOperations.execute()
146177
expect(lambda.getFunction).toBeCalledTimes(1)
147178
expect(lambda.createFunction).toBeCalledTimes(1)
179+
expect(lambda.waitFor).toBeCalledTimes(1)
180+
})
181+
182+
test('Deploy and config does not exist calls create but fails if status does not update', async () => {
183+
expect.assertions(4)
184+
const taskParameters = { ...baseTaskParameters }
185+
taskParameters.deploymentMode = deployCodeAndConfig
186+
taskParameters.roleARN = 'arn:yes'
187+
const lambda = new Lambda() as any
188+
lambda.getFunction = jest.fn(() => getFunctionFails)
189+
lambda.createFunction = jest.fn(() => updateFunctionSucceeds)
190+
lambda.waitFor = jest.fn(() => waitForFails)
191+
const taskOperations = new TaskOperations(new IAM(), lambda, taskParameters)
192+
await taskOperations.execute().catch(e => expect(`${e}`).toContain('waited too long'))
193+
expect(lambda.getFunction).toBeCalledTimes(1)
194+
expect(lambda.createFunction).toBeCalledTimes(1)
195+
expect(lambda.waitFor).toBeCalledTimes(1)
148196
})
149197

150198
test('Deploy and config exists calls update', async () => {
151-
expect.assertions(3)
199+
expect.assertions(4)
152200
const taskParameters = { ...baseTaskParameters }
153201
taskParameters.deploymentMode = deployCodeAndConfig
154202
taskParameters.roleARN = 'arn:yes'
155203
const lambda = new Lambda() as any
156204
lambda.getFunction = jest.fn(() => getFunctionSucceeds)
157205
lambda.updateFunctionCode = jest.fn(() => updateFunctionSucceeds)
158206
lambda.updateFunctionConfiguration = jest.fn(() => updateFunctionSucceeds)
207+
lambda.waitFor = jest.fn(() => waitForSucceeds)
159208
const taskOperations = new TaskOperations(new IAM(), lambda, taskParameters)
160209
await taskOperations.execute()
161210
expect(lambda.getFunction).toBeCalledTimes(1)
162211
expect(lambda.updateFunctionCode).toBeCalledTimes(1)
163212
expect(lambda.updateFunctionConfiguration).toBeCalledTimes(1)
213+
expect(lambda.waitFor).toBeCalledTimes(2)
214+
})
215+
216+
test('Deploy and config exists calls update but fails if status does not update after config update', async () => {
217+
expect.assertions(5)
218+
const taskParameters = { ...baseTaskParameters }
219+
taskParameters.deploymentMode = deployCodeAndConfig
220+
taskParameters.roleARN = 'arn:yes'
221+
const lambda = new Lambda() as any
222+
lambda.getFunction = jest.fn(() => getFunctionSucceeds)
223+
lambda.updateFunctionCode = jest.fn(() => updateFunctionSucceeds)
224+
lambda.updateFunctionConfiguration = jest.fn(() => updateFunctionSucceeds)
225+
lambda.waitFor = jest.fn(() => waitForFails)
226+
const taskOperations = new TaskOperations(new IAM(), lambda, taskParameters)
227+
await taskOperations.execute().catch(e => expect(`${e}`).toContain('waited too long'))
228+
expect(lambda.getFunction).toBeCalledTimes(1)
229+
expect(lambda.updateFunctionCode).toBeCalledTimes(0)
230+
expect(lambda.updateFunctionConfiguration).toBeCalledTimes(1)
231+
expect(lambda.waitFor).toBeCalledTimes(1)
232+
})
233+
234+
test('Deploy and config exists calls update but fails if status does not update after code update', async () => {
235+
expect.assertions(5)
236+
const taskParameters = { ...baseTaskParameters }
237+
taskParameters.deploymentMode = deployCodeAndConfig
238+
taskParameters.roleARN = 'arn:yes'
239+
const lambda = new Lambda() as any
240+
lambda.getFunction = jest.fn(() => getFunctionSucceeds)
241+
lambda.updateFunctionCode = jest.fn(() => updateFunctionSucceeds)
242+
lambda.updateFunctionConfiguration = jest.fn(() => updateFunctionSucceeds)
243+
lambda.waitFor = jest
244+
.fn()
245+
.mockReturnValueOnce(waitForSucceeds)
246+
.mockReturnValueOnce(waitForFails)
247+
const taskOperations = new TaskOperations(new IAM(), lambda, taskParameters)
248+
await taskOperations.execute().catch(e => expect(`${e}`).toContain('waited too long'))
249+
expect(lambda.getFunction).toBeCalledTimes(1)
250+
expect(lambda.updateFunctionCode).toBeCalledTimes(1)
251+
expect(lambda.updateFunctionConfiguration).toBeCalledTimes(1)
252+
expect(lambda.waitFor).toBeCalledTimes(2)
164253
})
165254

166255
test('Create function adds fields if they exist', async () => {
@@ -184,6 +273,7 @@ describe('Lambda Deploy Function', () => {
184273

185274
return updateFunctionSucceeds
186275
})
276+
lambda.waitFor = jest.fn(() => waitForSucceeds)
187277
const taskOperations = new TaskOperations(new IAM(), lambda, taskParameters)
188278
await taskOperations.execute()
189279
})
@@ -207,6 +297,7 @@ describe('Lambda Deploy Function', () => {
207297

208298
return updateFunctionSucceeds
209299
})
300+
lambda.waitFor = jest.fn(() => waitForSucceeds)
210301
lambda.tagResource = jest.fn(args => {
211302
expect(args.Tags).toStrictEqual({ tag1: '5', tag: 'abc' })
212303

@@ -227,6 +318,7 @@ describe('Lambda Deploy Function', () => {
227318
lambda.getFunction = jest.fn(() => getFunctionSucceeds)
228319
lambda.updateFunctionCode = jest.fn(() => updateFunctionSucceeds)
229320
lambda.updateFunctionConfiguration = jest.fn(() => updateFunctionSucceeds)
321+
lambda.waitFor = jest.fn(() => waitForSucceeds)
230322
const tagResourceFunction = jest.fn()
231323
lambda.tagResource = tagResourceFunction
232324
const taskOperations = new TaskOperations(new IAM(), lambda, taskParameters)

0 commit comments

Comments
 (0)