@ashishpandeyone/cdk-lambda-web-function 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
 
17
17
  <!--END STABILITY BANNER-->
18
18
 
19
- [AWS Lambda Web Functions](https://aws.amazon.com/lambda/) run an ordinary Node.js HTTP server (Express, Fastify, Hono, `node:http`) on Lambda and give it an HTTPS endpoint, optionally served from several regions. `WebFunction` deploys one, following the conventions of `aws-cdk-lib/aws-lambda` where the two services overlap.
19
+ [AWS Lambda Web Functions](https://docs.aws.amazon.com/lambda/latest/dg/web-functions.html) run an ordinary Node.js HTTP server (Express, Fastify, Hono, `node:http`) on Lambda and give it an HTTPS endpoint, optionally served from several regions. `WebFunction` deploys one, following the conventions of `aws-cdk-lib/aws-lambda` where the two services overlap.
20
20
 
21
21
  ```ts fixture=bare
22
22
  import * as lambda from 'aws-cdk-lib/aws-lambda';
@@ -37,10 +37,18 @@ new CfnOutput(this, 'Url', { value: fn.endpoint.url }); // https://<id>.lambda-w
37
37
 
38
38
  The stack's region is the function's **home region**. Multi-region endpoints need a stack with an explicit `env.region`.
39
39
 
40
+ ## Installation
41
+
42
+ ```bash
43
+ npm install @ashishpandeyone/cdk-lambda-web-function
44
+ ```
45
+
46
+ It needs `aws-cdk-lib` 2.273.0 or later and `constructs` 10.5.1 or later (peer dependencies), and Node.js 22 or later. The package is published for TypeScript and JavaScript only.
47
+
40
48
  ## Writing the server
41
49
 
42
50
  1. Listen on the host and port in **`AWS_LAMBDA_HTTP_ENDPOINT`** (`0.0.0.0:3000` today). `PORT` is not set.
43
- 2. Start listening within **about 9 seconds**, or initialisation fails with `Application.TimeoutListenError`.
51
+ 2. Start listening within **10 seconds**, or initialisation fails with `Application.TimeoutListenError`.
44
52
  3. Put the entry file at the root of your bundle as **`index.js`**, or set `entrypoint`.
45
53
  4. The runtime has **no AWS SDK**: bundle the clients you import.
46
54
  5. Code runs on **arm64**.
@@ -148,6 +156,7 @@ import { SignatureV4a } from '@smithy/signature-v4a';
148
156
  import { Sha256 } from '@aws-crypto/sha256-js';
149
157
  import { fromNodeProviderChain } from '@aws-sdk/credential-providers';
150
158
 
159
+ const host = new URL(url).hostname; // url: the endpoint's URL
151
160
  const signer = new SignatureV4a({ service: 'lambda', region: '*', sha256: Sha256, credentials: fromNodeProviderChain() });
152
161
  const signed = await signer.sign(
153
162
  { method: 'GET', protocol: 'https:', hostname: host, path: '/', headers: { host } },
@@ -156,6 +165,51 @@ const signed = await signer.sign(
156
165
  const response = await fetch(`https://${host}/`, { headers: signed.headers });
157
166
  ```
158
167
 
168
+ ### Other accounts and AWS services
169
+
170
+ A caller in another account needs permission twice: in its own IAM policy, and in the function's **resource policy**. `grantInvoke` handles the function's side: a role in the function's account only gets an IAM policy statement, while a principal in another account or an AWS service also gets a statement in the resource policy.
171
+
172
+ ```ts
173
+ fn.grantInvoke(iam.Role.fromRoleArn(this, 'Partner', 'arn:aws:iam::444455556666:role/caller'));
174
+ ```
175
+
176
+ For principals you can't grant to (a whole account, an organization, an AWS service with conditions), use `addPermission`. The `id` becomes the statement's `Sid`:
177
+
178
+ ```ts
179
+ fn.addPermission('PartnerAccount', { principal: new iam.AccountPrincipal('444455556666') });
180
+ fn.addPermission('MyOrg', { principal: new iam.OrganizationPrincipal('o-a1b2c3d4e5'), endpoint: fn.endpoint });
181
+ fn.addPermission('Cdn', {
182
+ principal: new iam.ServicePrincipal('cloudfront.amazonaws.com'),
183
+ sourceArn: `arn:aws:cloudfront::${this.account}:distribution/EDFDVBD6EXAMPLE`,
184
+ });
185
+ ```
186
+
187
+ `addPermission` allows calling every endpoint unless you pass `endpoint`. For other actions, such as letting another account create revisions, use `fn.addToResourcePolicy(statement)`. Statements use the endpoint ARN for invoking and the function ARN for actions on the function.
188
+
189
+ - **One resource policy per function, written by the function's stack.** The construct creates it (`AWS::Lambda::ResourcePolicy`) with the first statement. CloudFormation can't attach a second one: another stack, or a `CfnResourcePolicy` of your own on the function, fails at change-set validation with "already exists". If you attached one yourself, move its statements into `addPermission` or `addToResourcePolicy`. Each update of the policy replaces any statements added outside CloudFormation.
190
+ - **Public policies are rejected.** Lambda refuses a policy that lets anyone call the function, and the construct fails synthesis first. A `*` principal is only accepted with a condition that pins the caller, such as `aws:PrincipalOrgID` (`iam.OrganizationPrincipal`) or `aws:SourceAccount`. For a public endpoint, use `authType: NONE`.
191
+ - **Scope AWS services to your resource.** Lambda accepts a service principal with no condition, which would let that service call the endpoint on behalf of any account. The construct warns unless you give `sourceArn` or `sourceAccount` (or an `aws:SourceArn`, `aws:SourceAccount`, `aws:SourceOrgID` or `aws:SourceOrgPaths` condition).
192
+ - Lambda copies the policy to every region the function's endpoints serve from. The copy is eventually consistent, and in tests changes applied in every region within seconds.
193
+
194
+ ### Functions defined elsewhere
195
+
196
+ Import a Web Function defined in another stack or app to grant access to it, read its metrics, or reference its revisions:
197
+
198
+ ```ts
199
+ const orders = WebFunction.fromWebFunctionArn(this, 'Orders', 'arn:aws:lambda:us-east-1:111122223333:web-function/orders');
200
+ orders.grantInvoke(consumerRole); // every endpoint of the function
201
+
202
+ const live = WebFunction.fromWebFunctionAttributes(this, 'Live', {
203
+ webFunctionName: 'orders', // in this stack's account and region
204
+ endpointName: 'live', // grantInvoke allows this endpoint only
205
+ });
206
+ live.metricConcurrentRequests().createAlarm(this, 'Busy', { threshold: 60, evaluationPeriods: 3 });
207
+ ```
208
+
209
+ An imported function's metrics default to its own region. Its endpoints, revisions and log groups stay with the stack that defines it.
210
+
211
+ An imported function's `grantInvoke` adds to the caller's IAM policy only, which is all a caller in the function's account needs. A caller in another account also needs the function's resource policy to allow it, which only the function's own stack can change: `addToResourcePolicy` on an imported function adds nothing and warns.
212
+
159
213
  ## Configuration
160
214
 
161
215
  ```ts
@@ -175,6 +229,7 @@ api.addEnvironment('FEATURE_FLAG', 'on');
175
229
  table.grantReadWriteData(api); // the default role trusts lambda.amazonaws.com
176
230
  ```
177
231
 
232
+ - **Each execution environment has 2 vCPUs and 2 GB of memory.** There's no memory setting as on Lambda functions; scale with `maxConcurrencyPerEnvironment` and more environments instead.
178
233
  - Requests handled by one environment share module-level state, so keep handlers concurrency-safe.
179
234
  - Environment variable names starting with `AWS_` are reserved.
180
235
  - **Renaming the function replaces it and changes its URL.** A generated name depends on the construct path, so moving the construct renames it too.
@@ -207,7 +262,9 @@ new NodejsWebFunction(this, 'Api', {
207
262
 
208
263
  `code` takes any `lambda.Code` that produces a zip in S3: `fromAsset`, `fromBucket`/`fromBucketV2`, `fromCustomCommand`, `fromDockerBuild`. Inline code and container images fail at synth time.
209
264
 
210
- Lambda Web Functions download code as the `lambda.amazonaws.com` service principal, from a **versioned** bucket, and the CDK bootstrap bucket allows neither. So each stack gets a small code bucket, and the code is copied into it on deploy.
265
+ Lambda Web Functions download code as the `lambda.amazonaws.com` service principal, from a **versioned** bucket, and the CDK bootstrap bucket allows neither. So each stack gets a small code bucket, and the code is copied into it on deploy. The bucket is emptied and deleted with the stack.
266
+
267
+ Copying code, managing the function's log groups in every region, looking up regional URLs, cleaning up old revisions, creating rollback alarms in other regions and emptying the code bucket all run in one custom resource Lambda per stack. Its logs go to a log group the stack owns, kept for a month and deleted with the stack.
211
268
 
212
269
  ## Revisions
213
270
 
@@ -343,7 +400,7 @@ Web Functions log in two tiers:
343
400
  └──────────────────┘ └──────────────────────────────────────────┘
344
401
  ```
345
402
 
346
- **Query `fn.logGroup`**, the home-region group that receives every region's logs. The service would create missing groups without retention and never delete them, so the construct creates and owns all of them: both home-region groups, and `<group>.local` in every other region of the endpoint.
403
+ **Query `fn.logGroup`**, the home-region group that receives every region's logs. The service would create missing groups without retention and never delete them, so the construct creates and owns all of them: both home-region groups, and `<group>.local` in every other region any of its endpoints serves from.
347
404
 
348
405
  ```ts
349
406
  new WebFunction(this, 'Api', {
@@ -364,10 +421,10 @@ new WebFunction(this, 'Api', {
364
421
 
365
422
  ## Limitations
366
423
 
367
- - Not yet supported: importing existing functions, VPC access (network connectors), resource-policy permissions, KMS encryption, metrics helpers, tracing, and a CloudFront origin.
368
- - `cdk deploy --hotswap` and `cdk watch` don't support Web Functions.
424
+ - Not yet supported: VPC access (network connectors), KMS encryption, tracing, and a CloudFront origin.
425
+ - **Hotswap skips Web Functions without a warning.** `cdk deploy --hotswap` and `cdk watch`, which hotswaps by default, report `(no changes)` for a code or configuration change and the endpoint keeps serving the old revision. Use `cdk watch --hotswap-fallback` (or `cdk deploy --hotswap-fallback`): it deploys Web Function changes through CloudFormation, in about 40 seconds for a home-region endpoint.
426
+ - Fixed limits: 512 MB of code (unzipped) and 32 KB of environment variables per revision, 512 MB of `/tmp` per execution environment, and 128 KiB of headers per request.
369
427
  - Quotas: 10 endpoints and about 50 revisions per function (Lambda cleans up older revisions automatically), and 10,000 requests per second per endpoint. Per account and region: 100,000 requests per second and 2,000 Arm64 vCPUs (each execution environment uses 2). The account quotas are adjustable, and new accounts start lower. See [Lambda quotas](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html).
370
- - The stack's custom resource provider Lambda keeps its own log group after the stack is deleted, as with other CDK custom resources.
371
428
 
372
429
  ## Development
373
430