-
Notifications
You must be signed in to change notification settings - Fork 4k
/
Copy pathbase.ts
139 lines (127 loc) · 4.04 KB
/
base.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/* eslint-disable no-console */
import * as https from 'https';
import * as url from 'url';
import { SFN, StartExecutionInput } from '@aws-sdk/client-sfn';
interface HandlerResponse {
readonly status: 'SUCCESS' | 'FAILED';
readonly reason: 'OK' | string;
readonly data?: any;
}
// eslint-disable-next-line @typescript-eslint/ban-types
export abstract class CustomResourceHandler<Request extends object, Response extends object> {
public readonly physicalResourceId: string;
private readonly timeout: NodeJS.Timeout;
private timedOut = false;
constructor(protected readonly event: AWSLambda.CloudFormationCustomResourceEvent, protected readonly context: AWSLambda.Context) {
this.timeout = setTimeout(async () => {
await this.respond({
status: 'FAILED',
reason: 'Lambda Function Timeout',
data: this.context.logStreamName,
});
this.timedOut = true;
}, context.getRemainingTimeInMillis() - 1200);
this.event = event;
this.physicalResourceId = extractPhysicalResourceId(event);
}
/**
* Handles executing the custom resource event. If `stateMachineArn` is present
* in the props then trigger the waiter statemachine
*/
public async handle(): Promise<Response | undefined> {
try {
if ('stateMachineArn' in this.event.ResourceProperties) {
const req: StartExecutionInput = {
stateMachineArn: this.event.ResourceProperties.stateMachineArn,
name: this.event.RequestId,
input: JSON.stringify(this.event),
};
await this.startExecution(req);
return;
} else {
const response = await this.processEvent(this.event.ResourceProperties as unknown as Request);
return response;
}
} catch (e) {
console.log(e);
throw e;
} finally {
clearTimeout(this.timeout);
}
}
/**
* Handle async requests from the waiter state machine
*/
public async handleIsComplete(): Promise<Response | undefined> {
try {
const result = await this.processEvent(this.event.ResourceProperties as unknown as Request);
return result;
} catch (e) {
console.log(e);
return;
} finally {
clearTimeout(this.timeout);
}
}
/**
* Start a step function state machine which will wait for the request
* to be successful.
*/
private async startExecution(req: StartExecutionInput): Promise<void> {
try {
const sfn = new SFN({});
await sfn.startExecution(req);
} finally {
clearTimeout(this.timeout);
}
}
protected abstract processEvent(request: Request): Promise<Response | undefined>;
public respond(response: HandlerResponse) {
if (this.timedOut) {
return;
}
const cfResponse: AWSLambda.CloudFormationCustomResourceResponse = {
Status: response.status,
Reason: response.reason,
PhysicalResourceId: this.physicalResourceId,
StackId: this.event.StackId,
RequestId: this.event.RequestId,
LogicalResourceId: this.event.LogicalResourceId,
NoEcho: false,
Data: response.data,
};
const responseBody = JSON.stringify(cfResponse);
console.log('Responding to CloudFormation', responseBody);
const parsedUrl = url.parse(this.event.ResponseURL);
const requestOptions = {
hostname: parsedUrl.hostname,
path: parsedUrl.path,
method: 'PUT',
headers: {
'content-type': '',
'content-length': Buffer.byteLength(responseBody, 'utf8'),
},
};
return new Promise((resolve, reject) => {
try {
const request = https.request(requestOptions, resolve);
request.on('error', reject);
request.write(responseBody);
request.end();
} catch (e) {
reject(e);
} finally {
clearTimeout(this.timeout);
}
});
}
}
function extractPhysicalResourceId(event: AWSLambda.CloudFormationCustomResourceEvent): string {
switch (event.RequestType) {
case 'Create':
return event.LogicalResourceId;
case 'Update':
case 'Delete':
return event.PhysicalResourceId;
}
}