-
Notifications
You must be signed in to change notification settings - Fork 4k
/
Copy pathlambda-version.ts
308 lines (268 loc) · 9.92 KB
/
lambda-version.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
import { Construct } from 'constructs';
import { Alias, AliasOptions } from './alias';
import { Architecture } from './architecture';
import { EventInvokeConfigOptions } from './event-invoke-config';
import { Function } from './function';
import { IFunction, QualifiedFunctionBase } from './function-base';
import { CfnVersion } from './lambda.generated';
import { addAlias } from './util';
import * as cloudwatch from '../../aws-cloudwatch';
import { Fn, Lazy, RemovalPolicy } from '../../core';
export interface IVersion extends IFunction {
/**
* The most recently deployed version of this function.
* @attribute
*/
readonly version: string;
/**
* The underlying AWS Lambda function.
*/
readonly lambda: IFunction;
/**
* The ARN of the version for Lambda@Edge.
*/
readonly edgeArn: string;
/**
* Defines an alias for this version.
* @param aliasName The name of the alias
* @param options Alias options
*
* @deprecated Calling `addAlias` on a `Version` object will cause the Alias to be replaced on every function update. Call `function.addAlias()` or `new Alias()` instead.
*/
addAlias(aliasName: string, options?: AliasOptions): Alias;
}
/**
* Options for `lambda.Version`
*/
export interface VersionOptions extends EventInvokeConfigOptions {
/**
* SHA256 of the version of the Lambda source code
*
* Specify to validate that you're deploying the right version.
*
* @default No validation is performed
*/
readonly codeSha256?: string;
/**
* Description of the version
*
* @default Description of the Lambda
*/
readonly description?: string;
/**
* Specifies a provisioned concurrency configuration for a function's version.
*
* @default No provisioned concurrency
*/
readonly provisionedConcurrentExecutions?: number;
/**
* Whether to retain old versions of this function when a new version is
* created.
*
* @default RemovalPolicy.DESTROY
*/
readonly removalPolicy?: RemovalPolicy;
}
/**
* Properties for a new Lambda version
*/
export interface VersionProps extends VersionOptions {
/**
* Function to get the value of
*/
readonly lambda: IFunction;
}
export interface VersionAttributes {
/**
* The version.
*/
readonly version: string;
/**
* The lambda function.
*/
readonly lambda: IFunction;
}
/**
* Tag the current state of a Function with a Version number
*
* Avoid using this resource directly. If you need a Version object, use
* `function.currentVersion` instead. That will add a Version object to your
* template, and make sure the Version is invalidated whenever the Function
* object changes. If you use the `Version` resource directly, you are
* responsible for making sure it is invalidated (by changing its
* logical ID) whenever necessary.
*
* Version resources can then be used in `Alias` resources to refer to a
* particular deployment of a Lambda.
*
* If you want to ensure that you're associating the right version with
* the right deployment, specify the `codeSha256` property while
* creating the `Version.
*/
export class Version extends QualifiedFunctionBase implements IVersion {
/**
* Construct a Version object from a Version ARN.
*
* @param scope The cdk scope creating this resource
* @param id The cdk id of this resource
* @param versionArn The version ARN to create this version from
*/
public static fromVersionArn(scope: Construct, id: string, versionArn: string): IVersion {
const version = extractQualifierFromArn(versionArn);
const lambda = Function.fromFunctionArn(scope, `${id}Function`, versionArn);
class Import extends QualifiedFunctionBase implements IVersion {
public readonly version = version;
public readonly lambda = lambda;
public readonly functionName = `${lambda.functionName}:${version}`;
public readonly functionArn = versionArn;
public readonly grantPrincipal = lambda.grantPrincipal;
public readonly role = lambda.role;
public readonly architecture = lambda.architecture;
protected readonly qualifier = version;
protected readonly canCreatePermissions = this._isStackAccount();
public addAlias(name: string, opts: AliasOptions = {}): Alias {
return addAlias(this, this, name, opts);
}
public get edgeArn(): string {
if (version === '$LATEST') {
throw new Error('$LATEST function version cannot be used for Lambda@Edge');
}
return this.functionArn;
}
}
return new Import(scope, id);
}
public static fromVersionAttributes(scope: Construct, id: string, attrs: VersionAttributes): IVersion {
class Import extends QualifiedFunctionBase implements IVersion {
public readonly version = attrs.version;
public readonly lambda = attrs.lambda;
public readonly functionName = `${attrs.lambda.functionName}:${attrs.version}`;
public readonly functionArn = `${attrs.lambda.functionArn}:${attrs.version}`;
public readonly grantPrincipal = attrs.lambda.grantPrincipal;
public readonly role = attrs.lambda.role;
public readonly architecture = attrs.lambda.architecture;
protected readonly qualifier = attrs.version;
protected readonly canCreatePermissions = this._isStackAccount();
public addAlias(name: string, opts: AliasOptions = {}): Alias {
return addAlias(this, this, name, opts);
}
public get edgeArn(): string {
if (attrs.version === '$LATEST') {
throw new Error('$LATEST function version cannot be used for Lambda@Edge');
}
return this.functionArn;
}
}
return new Import(scope, id);
}
public readonly version: string;
public readonly lambda: IFunction;
public readonly functionArn: string;
public readonly functionName: string;
public readonly architecture: Architecture;
protected readonly qualifier: string;
protected readonly canCreatePermissions = true;
constructor(scope: Construct, id: string, props: VersionProps) {
super(scope, id);
this.lambda = props.lambda;
this.architecture = props.lambda.architecture;
const version = new CfnVersion(this, 'Resource', {
codeSha256: props.codeSha256,
description: props.description,
functionName: props.lambda.functionName,
provisionedConcurrencyConfig: this.determineProvisionedConcurrency(props),
});
if (props.removalPolicy) {
version.applyRemovalPolicy(props.removalPolicy, {
default: RemovalPolicy.DESTROY,
});
}
this.version = version.attrVersion;
this.functionArn = version.ref;
this.functionName = `${this.lambda.functionName}:${this.version}`;
this.qualifier = version.attrVersion;
if (props.onFailure || props.onSuccess || props.maxEventAge || props.retryAttempts !== undefined) {
this.configureAsyncInvoke({
onFailure: props.onFailure,
onSuccess: props.onSuccess,
maxEventAge: props.maxEventAge,
retryAttempts: props.retryAttempts,
});
}
}
public get grantPrincipal() {
return this.lambda.grantPrincipal;
}
public get role() {
return this.lambda.role;
}
public metric(metricName: string, props: cloudwatch.MetricOptions = {}): cloudwatch.Metric {
// Metrics on Aliases need the "bare" function name, and the alias' ARN, this differs from the base behavior.
return super.metric(metricName, {
dimensions: {
FunctionName: this.lambda.functionName,
// construct the ARN from the underlying lambda so that alarms on an alias
// don't cause a circular dependency with CodeDeploy
// see: https://github.com/aws/aws-cdk/issues/2231
Resource: `${this.lambda.functionArn}:${this.version}`,
},
...props,
});
}
/**
* Defines an alias for this version.
* @param aliasName The name of the alias (e.g. "live")
* @param options Alias options
* @deprecated Calling `addAlias` on a `Version` object will cause the Alias to be replaced on every function update. Call `function.addAlias()` or `new Alias()` instead.
*/
public addAlias(aliasName: string, options: AliasOptions = {}): Alias {
return addAlias(this, this, aliasName, options);
}
public get edgeArn(): string {
// Validate first that this version can be used for Lambda@Edge
if (this.version === '$LATEST') {
throw new Error('$LATEST function version cannot be used for Lambda@Edge');
}
// Check compatibility at synthesis. It could be that the version was associated
// with a CloudFront distribution first and made incompatible afterwards.
return Lazy.string({
produce: () => {
// Validate that the underlying function can be used for Lambda@Edge
if (this.lambda instanceof Function) {
this.lambda._checkEdgeCompatibility();
}
return this.functionArn;
},
});
}
/**
* Validate that the provisionedConcurrentExecutions makes sense
*
* Member must have value greater than or equal to 1
*/
private determineProvisionedConcurrency(props: VersionProps): CfnVersion.ProvisionedConcurrencyConfigurationProperty | undefined {
if (!props.provisionedConcurrentExecutions) {
return undefined;
}
if (props.provisionedConcurrentExecutions <= 0) {
throw new Error('provisionedConcurrentExecutions must have value greater than or equal to 1');
}
return { provisionedConcurrentExecutions: props.provisionedConcurrentExecutions };
}
}
/**
* Given an opaque (token) ARN, returns a CloudFormation expression that extracts the
* qualifier (= version or alias) from the ARN.
*
* Version ARNs look like this:
*
* arn:aws:lambda:region:account-id:function:function-name:qualifier
*
* ..which means that in order to extract the `qualifier` component from the ARN, we can
* split the ARN using ":" and select the component in index 7.
*
* @returns `FnSelect(7, FnSplit(':', arn))`
*/
export function extractQualifierFromArn(arn: string) {
return Fn.select(7, Fn.split(':', arn));
}