-
Notifications
You must be signed in to change notification settings - Fork 4k
/
function-base.ts
351 lines (300 loc) · 10.3 KB
/
function-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
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 cloudwatch = require('@aws-cdk/aws-cloudwatch');
import ec2 = require('@aws-cdk/aws-ec2');
import events = require('@aws-cdk/aws-events');
import iam = require('@aws-cdk/aws-iam');
import logs = require('@aws-cdk/aws-logs');
import s3n = require('@aws-cdk/aws-s3-notifications');
import stepfunctions = require('@aws-cdk/aws-stepfunctions');
import cdk = require('@aws-cdk/cdk');
import { IEventSource } from './event-source';
import { CfnPermission } from './lambda.generated';
import { Permission } from './permission';
import { CommonPipelineInvokeActionProps, PipelineInvokeAction } from './pipeline-action';
export interface IFunction extends cdk.IConstruct, events.IEventRuleTarget, logs.ILogSubscriptionDestination,
s3n.IBucketNotificationDestination, ec2.IConnectable, stepfunctions.IStepFunctionsTaskResource {
/**
* Logical ID of this Function.
*/
readonly id: string;
/**
* The name of the function.
*/
readonly functionName: string;
/**
* The ARN fo the function.
*/
readonly functionArn: string;
/**
* The IAM role associated with this function.
*/
readonly role?: iam.IRole;
/**
* Whether or not this Lambda function was bound to a VPC
*
* If this is is `false`, trying to access the `connections` object will fail.
*/
readonly isBoundToVpc: boolean;
/**
* Adds a permission to the Lambda resource policy.
* @param id The id ƒor the permission construct
*/
addPermission(id: string, permission: Permission): void;
/**
* Convenience method for creating a new {@link PipelineInvokeAction}.
*
* @param props the construction properties of the new Action
* @returns the newly created {@link PipelineInvokeAction}
*/
toCodePipelineInvokeAction(props: CommonPipelineInvokeActionProps): PipelineInvokeAction;
addToRolePolicy(statement: iam.PolicyStatement): void;
/**
* Grant the given identity permissions to invoke this Lambda
*/
grantInvoke(identity?: iam.IPrincipal): void;
/**
* Return the given named metric for this Lambda
*/
metric(metricName: string, props?: cloudwatch.MetricCustomization): cloudwatch.Metric;
/**
* Metric for the Duration of this Lambda
*
* @default average over 5 minutes
*/
metricDuration(props?: cloudwatch.MetricCustomization): cloudwatch.Metric;
/**
* Metric for the number of invocations of this Lambda
*
* @default sum over 5 minutes
*/
metricInvocations(props?: cloudwatch.MetricCustomization): cloudwatch.Metric;
/**
* Metric for the number of throttled invocations of this Lambda
*
* @default sum over 5 minutes
*/
metricThrottles(props?: cloudwatch.MetricCustomization): cloudwatch.Metric;
/**
* Export this Function (without the role)
*/
export(): FunctionImportProps;
addEventSource(source: IEventSource): void;
}
/**
* Represents a Lambda function defined outside of this stack.
*/
export interface FunctionImportProps {
/**
* The ARN of the Lambda function.
*
* Format: arn:<partition>:lambda:<region>:<account-id>:function:<function-name>
*/
functionArn: string;
/**
* The IAM execution role associated with this function.
*
* If the role is not specified, any role-related operations will no-op.
*/
role?: iam.IRole;
/**
* Id of the securityGroup for this Lambda, if in a VPC.
*
* This needs to be given in order to support allowing connections
* to this Lambda.
*/
securityGroupId?: string;
}
export abstract class FunctionBase extends cdk.Construct implements IFunction {
/**
* The name of the function.
*/
public abstract readonly functionName: string;
/**
* The ARN fo the function.
*/
public abstract readonly functionArn: string;
/**
* The IAM role associated with this function.
*/
public abstract readonly role?: iam.IRole;
/**
* Whether the addPermission() call adds any permissions
*
* True for new Lambdas, false for imported Lambdas (they might live in different accounts).
*/
protected abstract readonly canCreatePermissions: boolean;
/**
* Actual connections object for this Lambda
*
* May be unset, in which case this Lambda is not configured use in a VPC.
*/
protected _connections?: ec2.Connections;
/**
* Indicates if the policy that allows CloudWatch logs to publish to this lambda has been added.
*/
private logSubscriptionDestinationPolicyAddedFor: string[] = [];
/**
* Adds a permission to the Lambda resource policy.
* @param id The id ƒor the permission construct
*/
public addPermission(id: string, permission: Permission) {
if (!this.canCreatePermissions) {
// FIXME: Report metadata
return;
}
const principal = this.parsePermissionPrincipal(permission.principal);
const action = permission.action || 'lambda:InvokeFunction';
new CfnPermission(this, id, {
action,
principal,
functionName: this.functionName,
eventSourceToken: permission.eventSourceToken,
sourceAccount: permission.sourceAccount,
sourceArn: permission.sourceArn,
});
}
public get id() {
return this.node.id;
}
public toCodePipelineInvokeAction(props: CommonPipelineInvokeActionProps): PipelineInvokeAction {
return new PipelineInvokeAction({
...props,
lambda: this,
});
}
public addToRolePolicy(statement: iam.PolicyStatement) {
if (!this.role) {
return;
}
this.role.addToPolicy(statement);
}
/**
* Access the Connections object
*
* Will fail if not a VPC-enabled Lambda Function
*/
public get connections(): ec2.Connections {
if (!this._connections) {
// tslint:disable-next-line:max-line-length
throw new Error('Only VPC-associated Lambda Functions have security groups to manage. Supply the "vpc" parameter when creating the Lambda, or "securityGroupId" when importing it.');
}
return this._connections;
}
/**
* Whether or not this Lambda function was bound to a VPC
*
* If this is is `false`, trying to access the `connections` object will fail.
*/
public get isBoundToVpc(): boolean {
return !!this._connections;
}
/**
* Returns a RuleTarget that can be used to trigger this Lambda as a
* result from a CloudWatch event.
*/
public asEventRuleTarget(ruleArn: string, ruleId: string): events.EventRuleTargetProps {
const permissionId = `AllowEventRule${ruleId}`;
if (!this.node.tryFindChild(permissionId)) {
this.addPermission(permissionId, {
action: 'lambda:InvokeFunction',
principal: new iam.ServicePrincipal('events.amazonaws.com'),
sourceArn: ruleArn
});
}
return {
id: this.node.id,
arn: this.functionArn,
};
}
/**
* Grant the given identity permissions to invoke this Lambda
*/
public grantInvoke(identity?: iam.IPrincipal) {
if (identity) {
identity.addToPolicy(new iam.PolicyStatement()
.addAction('lambda:InvokeFunction')
.addResource(this.functionArn));
}
}
public logSubscriptionDestination(sourceLogGroup: logs.ILogGroup): logs.LogSubscriptionDestination {
const arn = sourceLogGroup.logGroupArn;
if (this.logSubscriptionDestinationPolicyAddedFor.indexOf(arn) === -1) {
// NOTE: the use of {AWS::Region} limits this to the same region, which shouldn't really be an issue,
// since the Lambda must be in the same region as the SubscriptionFilter anyway.
//
// (Wildcards in principals are unfortunately not supported.
this.addPermission('InvokedByCloudWatchLogs', {
principal: new iam.ServicePrincipal(`logs.${this.node.stack.region}.amazonaws.com`),
sourceArn: arn
});
this.logSubscriptionDestinationPolicyAddedFor.push(arn);
}
return { arn: this.functionArn };
}
/**
* Export this Function (without the role)
*/
public abstract export(): FunctionImportProps;
/**
* Allows this Lambda to be used as a destination for bucket notifications.
* Use `bucket.onEvent(lambda)` to subscribe.
*/
public asBucketNotificationDestination(bucketArn: string, bucketId: string): s3n.BucketNotificationDestinationProps {
const permissionId = `AllowBucketNotificationsFrom${bucketId}`;
if (!this.node.tryFindChild(permissionId)) {
this.addPermission(permissionId, {
sourceAccount: this.node.stack.accountId,
principal: new iam.ServicePrincipal('s3.amazonaws.com'),
sourceArn: bucketArn,
});
}
// if we have a permission resource for this relationship, add it as a dependency
// to the bucket notifications resource, so it will be created first.
const permission = this.node.tryFindChild(permissionId) as cdk.CfnResource;
return {
type: s3n.BucketNotificationDestinationType.Lambda,
arn: this.functionArn,
dependencies: permission ? [ permission ] : undefined
};
}
public asStepFunctionsTaskResource(_callingTask: stepfunctions.Task): stepfunctions.StepFunctionsTaskResourceProps {
return {
resourceArn: this.functionArn,
metricPrefixSingular: 'LambdaFunction',
metricPrefixPlural: 'LambdaFunctions',
metricDimensions: { LambdaFunctionArn: this.functionArn },
policyStatements: [new iam.PolicyStatement()
.addResource(this.functionArn)
.addActions("lambda:InvokeFunction")
]
};
}
/**
* Adds an event source to this function.
*
* Event sources are implemented in the @aws-cdk/aws-lambda-event-sources module.
*
* The following example adds an SQS Queue as an event source:
*
* import { SqsEventSource } from '@aws-cdk/aws-lambda-event-sources';
* myFunction.addEventSource(new SqsEventSource(myQueue));
*
* @param source The event source to bind to this function
*/
public addEventSource(source: IEventSource) {
source.bind(this);
}
private parsePermissionPrincipal(principal?: iam.PolicyPrincipal) {
if (!principal) {
return undefined;
}
// use duck-typing, not instance of
if ('accountId' in principal) {
return (principal as iam.AccountPrincipal).accountId;
}
if (`service` in principal) {
return (principal as iam.ServicePrincipal).service;
}
throw new Error(`Invalid principal type for Lambda permission statement: ${JSON.stringify(this.node.resolve(principal))}. ` +
'Supported: AccountPrincipal, ServicePrincipal');
}
}