-
Notifications
You must be signed in to change notification settings - Fork 3.7k
/
stage.ts
351 lines (306 loc) · 11.2 KB
/
stage.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
import { ArnFormat, Duration, IResource, Resource, Stack, Token } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { AccessLogFormat, IAccessLogDestination } from './access-log';
import { CfnStage } from './apigateway.generated';
import { Deployment } from './deployment';
import { IRestApi, RestApiBase } from './restapi';
import { parseMethodOptionsPath } from './util';
/**
* Represents an APIGateway Stage.
*/
export interface IStage extends IResource {
/**
* Name of this stage.
* @attribute
*/
readonly stageName: string;
/**
* RestApi to which this stage is associated.
*/
readonly restApi: IRestApi;
}
export interface StageOptions extends MethodDeploymentOptions {
/**
* The name of the stage, which API Gateway uses as the first path segment
* in the invoked Uniform Resource Identifier (URI).
*
* @default - "prod"
*/
readonly stageName?: string;
/**
* The CloudWatch Logs log group.
*
* @default - No destination
*/
readonly accessLogDestination?: IAccessLogDestination;
/**
* A single line format of access logs of data, as specified by selected $content variables.
* The format must include at least `AccessLogFormat.contextRequestId()`.
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html#context-variable-reference
*
* @default - Common Log Format
*/
readonly accessLogFormat?: AccessLogFormat;
/**
* Specifies whether Amazon X-Ray tracing is enabled for this method.
*
* @default false
*/
readonly tracingEnabled?: boolean;
/**
* Indicates whether cache clustering is enabled for the stage.
*
* @default - Disabled for the stage.
*/
readonly cacheClusterEnabled?: boolean;
/**
* The stage's cache cluster size.
* @default 0.5
*/
readonly cacheClusterSize?: string;
/**
* The identifier of the client certificate that API Gateway uses to call
* your integration endpoints in the stage.
*
* @default - None.
*/
readonly clientCertificateId?: string;
/**
* A description of the purpose of the stage.
*
* @default - No description.
*/
readonly description?: string;
/**
* The version identifier of the API documentation snapshot.
*
* @default - No documentation version.
*/
readonly documentationVersion?: string;
/**
* A map that defines the stage variables. Variable names must consist of
* alphanumeric characters, and the values must match the following regular
* expression: [A-Za-z0-9-._~:/?#&=,]+.
*
* @default - No stage variables.
*/
readonly variables?: { [key: string]: string };
/**
* Method deployment options for specific resources/methods. These will
* override common options defined in `StageOptions#methodOptions`.
*
* @param path is {resource_path}/{http_method} (i.e. /api/toys/GET) for an
* individual method override. You can use `*` for both {resource_path} and {http_method}
* to define options for all methods/resources.
*
* @default - Common options will be used.
*/
readonly methodOptions?: { [path: string]: MethodDeploymentOptions };
}
export interface StageProps extends StageOptions {
/**
* The deployment that this stage points to [disable-awslint:ref-via-interface].
*/
readonly deployment: Deployment;
}
export enum MethodLoggingLevel {
OFF = 'OFF',
ERROR = 'ERROR',
INFO = 'INFO'
}
export interface MethodDeploymentOptions {
/**
* Specifies whether Amazon CloudWatch metrics are enabled for this method.
*
* @default false
*/
readonly metricsEnabled?: boolean;
/**
* Specifies the logging level for this method, which effects the log
* entries pushed to Amazon CloudWatch Logs.
*
* @default - Off
*/
readonly loggingLevel?: MethodLoggingLevel;
/**
* Specifies whether data trace logging is enabled for this method.
* When enabled, API gateway will log the full API requests and responses.
* This can be useful to troubleshoot APIs, but can result in logging sensitive data.
* We recommend that you don't enable this feature for production APIs.
*
* @default false
*/
readonly dataTraceEnabled?: boolean;
/**
* Specifies the throttling burst limit.
* The total rate of all requests in your AWS account is limited to 5,000 requests.
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html
*
* @default - No additional restriction.
*/
readonly throttlingBurstLimit?: number;
/**
* Specifies the throttling rate limit.
* The total rate of all requests in your AWS account is limited to 10,000 requests per second (rps).
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-request-throttling.html
*
* @default - No additional restriction.
*/
readonly throttlingRateLimit?: number;
/**
* Specifies whether responses should be cached and returned for requests. A
* cache cluster must be enabled on the stage for responses to be cached.
*
* @default - Caching is Disabled.
*/
readonly cachingEnabled?: boolean;
/**
* Specifies the time to live (TTL), in seconds, for cached responses. The
* higher the TTL, the longer the response will be cached.
* @see https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-caching.html
*
* @default Duration.minutes(5)
*/
readonly cacheTtl?: Duration;
/**
* Indicates whether the cached responses are encrypted.
*
* @default false
*/
readonly cacheDataEncrypted?: boolean;
}
export class Stage extends Resource implements IStage {
public readonly stageName: string;
public readonly restApi: IRestApi;
private enableCacheCluster?: boolean;
constructor(scope: Construct, id: string, props: StageProps) {
super(scope, id);
this.enableCacheCluster = props.cacheClusterEnabled;
const methodSettings = this.renderMethodSettings(props); // this can mutate `this.cacheClusterEnabled`
// custom access logging
let accessLogSetting: CfnStage.AccessLogSettingProperty | undefined;
const accessLogDestination = props.accessLogDestination;
const accessLogFormat = props.accessLogFormat;
if (!accessLogDestination && !accessLogFormat) {
accessLogSetting = undefined;
} else {
if (accessLogFormat !== undefined &&
!Token.isUnresolved(accessLogFormat.toString()) &&
!/.*\$context.requestId.*/.test(accessLogFormat.toString())) {
throw new Error('Access log must include at least `AccessLogFormat.contextRequestId()`');
}
if (accessLogFormat !== undefined && accessLogDestination === undefined) {
throw new Error('Access log format is specified without a destination');
}
accessLogSetting = {
destinationArn: accessLogDestination?.bind(this).destinationArn,
format: accessLogFormat?.toString() ? accessLogFormat?.toString() : AccessLogFormat.clf().toString(),
};
}
// enable cache cluster if cacheClusterSize is set
if (props.cacheClusterSize !== undefined) {
if (this.enableCacheCluster === undefined) {
this.enableCacheCluster = true;
} else if (this.enableCacheCluster === false) {
throw new Error(`Cannot set "cacheClusterSize" to ${props.cacheClusterSize} and "cacheClusterEnabled" to "false"`);
}
}
const cacheClusterSize = this.enableCacheCluster ? (props.cacheClusterSize || '0.5') : undefined;
const resource = new CfnStage(this, 'Resource', {
stageName: props.stageName || 'prod',
accessLogSetting,
cacheClusterEnabled: this.enableCacheCluster,
cacheClusterSize,
clientCertificateId: props.clientCertificateId,
deploymentId: props.deployment.deploymentId,
restApiId: props.deployment.api.restApiId,
description: props.description,
documentationVersion: props.documentationVersion,
variables: props.variables,
tracingEnabled: props.tracingEnabled,
methodSettings,
});
this.stageName = resource.ref;
this.restApi = props.deployment.api;
if (RestApiBase._isRestApiBase(this.restApi)) {
this.restApi._attachStage(this);
}
}
/**
* Returns the invoke URL for a certain path.
* @param path The resource path
*/
public urlForPath(path: string = '/') {
if (!path.startsWith('/')) {
throw new Error(`Path must begin with "/": ${path}`);
}
return `https://${this.restApi.restApiId}.execute-api.${Stack.of(this).region}.${Stack.of(this).urlSuffix}/${this.stageName}${path}`;
}
/**
* Returns the resource ARN for this stage:
*
* arn:aws:apigateway:{region}::/restapis/{restApiId}/stages/{stageName}
*
* Note that this is separate from the execute-api ARN for methods and resources
* within this stage.
*
* @attribute
*/
public get stageArn() {
return Stack.of(this).formatArn({
arnFormat: ArnFormat.SLASH_RESOURCE_SLASH_RESOURCE_NAME,
service: 'apigateway',
account: '',
resource: 'restapis',
resourceName: `${this.restApi.restApiId}/stages/${this.stageName}`,
});
}
private renderMethodSettings(props: StageProps): CfnStage.MethodSettingProperty[] | undefined {
const settings = new Array<CfnStage.MethodSettingProperty>();
const self = this;
// extract common method options from the stage props
const commonMethodOptions: MethodDeploymentOptions = {
metricsEnabled: props.metricsEnabled,
loggingLevel: props.loggingLevel,
dataTraceEnabled: props.dataTraceEnabled,
throttlingBurstLimit: props.throttlingBurstLimit,
throttlingRateLimit: props.throttlingRateLimit,
cachingEnabled: props.cachingEnabled,
cacheTtl: props.cacheTtl,
cacheDataEncrypted: props.cacheDataEncrypted,
};
// if any of them are defined, add an entry for '/*/*'.
const hasCommonOptions = Object.keys(commonMethodOptions).map(v => (commonMethodOptions as any)[v]).filter(x => x).length > 0;
if (hasCommonOptions) {
settings.push(renderEntry('/*/*', commonMethodOptions));
}
if (props.methodOptions) {
for (const path of Object.keys(props.methodOptions)) {
settings.push(renderEntry(path, props.methodOptions[path]));
}
}
return settings.length === 0 ? undefined : settings;
function renderEntry(path: string, options: MethodDeploymentOptions): CfnStage.MethodSettingProperty {
if (options.cachingEnabled) {
if (self.enableCacheCluster === undefined) {
self.enableCacheCluster = true;
} else if (self.enableCacheCluster === false) {
throw new Error(`Cannot enable caching for method ${path} since cache cluster is disabled on stage`);
}
}
const { httpMethod, resourcePath } = parseMethodOptionsPath(path);
return {
httpMethod,
resourcePath,
cacheDataEncrypted: options.cacheDataEncrypted,
cacheTtlInSeconds: options.cacheTtl && options.cacheTtl.toSeconds(),
cachingEnabled: options.cachingEnabled,
dataTraceEnabled: options.dataTraceEnabled ?? false,
loggingLevel: options.loggingLevel,
metricsEnabled: options.metricsEnabled,
throttlingBurstLimit: options.throttlingBurstLimit,
throttlingRateLimit: options.throttlingRateLimit,
};
}
}
}