@aws-blocks/core 0.3.0 → 0.4.0
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 +13 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +32 -0
- package/dist/api.test.js +22 -0
- package/dist/bb-utils.d.ts +1 -0
- package/dist/bb-utils.d.ts.map +1 -1
- package/dist/bb-utils.js +3 -0
- package/dist/cdk/apigateway-account.d.ts +33 -0
- package/dist/cdk/apigateway-account.d.ts.map +1 -0
- package/dist/cdk/apigateway-account.js +60 -0
- package/dist/cdk/blocks-backend.d.ts +8 -0
- package/dist/cdk/blocks-backend.d.ts.map +1 -1
- package/dist/cdk/blocks-backend.js +17 -4
- package/dist/cdk/blocks-backend.test.js +17 -0
- package/dist/cdk/blocks-defaults.d.ts +65 -1
- package/dist/cdk/blocks-defaults.d.ts.map +1 -1
- package/dist/cdk/blocks-defaults.js +17 -1
- package/dist/cdk/blocks-defaults.test.js +17 -0
- package/dist/cdk/blocks-stack.test.js +21 -0
- package/dist/cdk/compute/compute-registry.d.ts +19 -0
- package/dist/cdk/compute/compute-registry.d.ts.map +1 -0
- package/dist/cdk/compute/compute-registry.js +38 -0
- package/dist/cdk/compute/compute.d.ts +2 -0
- package/dist/cdk/compute/compute.d.ts.map +1 -1
- package/dist/cdk/compute/compute.js +8 -0
- package/dist/cdk/compute/default-compute-factory.d.ts +1 -0
- package/dist/cdk/compute/default-compute-factory.d.ts.map +1 -1
- package/dist/cdk/config-registry.d.ts +34 -4
- package/dist/cdk/config-registry.d.ts.map +1 -1
- package/dist/cdk/config-registry.js +83 -25
- package/dist/cdk/config-registry.test.d.ts +2 -0
- package/dist/cdk/config-registry.test.d.ts.map +1 -0
- package/dist/cdk/config-registry.test.js +115 -0
- package/dist/cdk/index.d.ts +30 -2
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +42 -2
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/common/config.d.ts +34 -0
- package/dist/common/config.d.ts.map +1 -1
- package/dist/common/config.js +45 -3
- package/dist/common/config.test.js +19 -0
- package/dist/common/index.d.ts +8 -0
- package/dist/common/index.d.ts.map +1 -1
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +20 -0
- package/dist/hosting.d.ts +9 -0
- package/dist/hosting.d.ts.map +1 -1
- package/dist/hosting.js +10 -1
- package/dist/hosting.test.js +24 -0
- package/dist/index.cdk.d.ts +2 -2
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/lambda-handler.d.ts +17 -0
- package/dist/lambda-handler.d.ts.map +1 -1
- package/dist/lambda-handler.js +52 -3
- package/dist/lambda-handler.test.js +130 -1
- package/dist/scripts/sandbox-empty-buckets.test.d.ts +2 -0
- package/dist/scripts/sandbox-empty-buckets.test.d.ts.map +1 -0
- package/dist/scripts/sandbox-empty-buckets.test.js +171 -0
- package/dist/scripts/sandbox.d.ts +54 -0
- package/dist/scripts/sandbox.d.ts.map +1 -1
- package/dist/scripts/sandbox.js +163 -25
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -1
- package/src/api.test.ts +25 -0
- package/src/api.ts +39 -0
- package/src/bb-utils.ts +3 -0
- package/src/cdk/apigateway-account.ts +66 -0
- package/src/cdk/blocks-backend.test.ts +24 -0
- package/src/cdk/blocks-backend.ts +17 -4
- package/src/cdk/blocks-defaults.test.ts +21 -0
- package/src/cdk/blocks-defaults.ts +67 -1
- package/src/cdk/blocks-stack.test.ts +25 -0
- package/src/cdk/compute/compute-registry.ts +45 -0
- package/src/cdk/compute/compute.ts +10 -0
- package/src/cdk/compute/default-compute-factory.ts +1 -0
- package/src/cdk/config-registry.test.ts +135 -0
- package/src/cdk/config-registry.ts +92 -34
- package/src/cdk/index.ts +45 -2
- package/src/client/index.ts +1 -1
- package/src/common/config.test.ts +21 -0
- package/src/common/config.ts +47 -3
- package/src/common/index.ts +8 -0
- package/src/errors.ts +21 -0
- package/src/hosting.test.ts +28 -0
- package/src/hosting.ts +21 -1
- package/src/index.cdk.ts +4 -1
- package/src/index.ts +1 -1
- package/src/lambda-handler.test.ts +141 -1
- package/src/lambda-handler.ts +54 -2
- package/src/scripts/sandbox-empty-buckets.test.ts +191 -0
- package/src/scripts/sandbox.ts +185 -24
- package/src/version.ts +1 -1
package/README.md
CHANGED
|
@@ -313,6 +313,19 @@ new Hosting(stack, 'Web', {
|
|
|
313
313
|
|
|
314
314
|
The `framework` option selects the frontend type: `'spa' | 'static' | 'nextjs'`. When omitted, the framework is auto-detected by reading your app's OWN `package.json` (not `node_modules`): a `next` dependency → `nextjs`; otherwise `spa`; and `static` when there is no `package.json`. Set `framework: 'spa'` explicitly to override auto-detection — e.g. when a stray `next` dependency would otherwise trigger an unwanted Next.js/OpenNext build. Full reference lives in the source JSDoc.
|
|
315
315
|
|
|
316
|
+
Old builds are retained for rollback and expired by an S3 lifecycle rule after `buildRetentionDays` (default **30**). The build currently being served is never expired regardless of deploy cadence — only superseded builds are cleaned up. Only builds superseded by a normal `Update` cutover are auto-tagged (and thus auto-expired): pre-existing/orphaned builds and aborted or rolled-back deploys are never tagged and need manual cleanup (e.g. an S3 inventory / Batch Operations sweep). Raise the window with `buildRetentionDays` (it must be ≥ `skewProtection.maxAge` in days):
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
new Hosting(stack, 'Web', {
|
|
320
|
+
root: join(__dirname, '..'),
|
|
321
|
+
api: blocksStack,
|
|
322
|
+
buildRetentionDays: 90,
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Core's top-level `buildRetentionDays` maps to the L3 construct's `storage.buildRetentionDays`.
|
|
327
|
+
|
|
328
|
+
|
|
316
329
|
## Building Blocks
|
|
317
330
|
|
|
318
331
|
Import Building Blocks from their specific packages (or from the `@aws-blocks/blocks` umbrella):
|
package/dist/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE;QACP,OAAO,EAAE,OAAO,CAAC;QACjB,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;QACxC,IAAI,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5B;;;;;;;;;;WAUG;QACH,GAAG,EAAE,GAAG,CAAC;QACT;;;;;WAKG;QACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B;;;;;;;;;WASG;QACH,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,CAAC;IACF,QAAQ,EAAE;QACR,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;KAC3B,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,KAAK,CAAC,CAAC;AAG1G,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI;KAChE,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,IAAI,KAAK,MAAM,CAAC,GACzD,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GACtC,KAAK;CACV,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,eAAoC,CAAC;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG;IAC1B,OAAO,EAAE;QACP,OAAO,EAAE,OAAO,CAAC;QACjB,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;QACxC,IAAI,EAAE,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;QAC5B;;;;;;;;;;WAUG;QACH,GAAG,EAAE,GAAG,CAAC;QACT;;;;;WAKG;QACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B;;;;;;;;;WASG;QACH,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,CAAC;IACF,QAAQ,EAAE;QACR,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,CAAC;KAC3B,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,KAAK,CAAC,CAAC;AAG1G,KAAK,QAAQ,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,IAAI;KAChE,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,MAAM,IAAI,KAAK,MAAM,CAAC,GACzD,CAAC,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GACtC,KAAK;CACV,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,eAAoC,CAAC;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAsCrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0FG;AACH,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;CAChI;AAED,eAAO,MAAM,YAAY,EAAE,uBAQnB,CAAC"}
|
package/dist/api.js
CHANGED
|
@@ -2,9 +2,41 @@
|
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
/** Marker symbol to identify ApiNamespace instances during discovery. */
|
|
4
4
|
export const API_NAMESPACE_MARKER = Symbol.for('blocks:ApiNamespace');
|
|
5
|
+
/**
|
|
6
|
+
* Record this namespace on its resolved compute so request routing can later
|
|
7
|
+
* map a namespace to the compute that hosts it.
|
|
8
|
+
*
|
|
9
|
+
* CDK-synth concern only: in the CDK bundle `scope.compute` resolves to the
|
|
10
|
+
* namespace's compute (the stack default today), and we append the name to its
|
|
11
|
+
* `namespaces` list. In the mock/runtime bundles — or a scopeless test — the
|
|
12
|
+
* property is absent, so `scope.compute` is `undefined` and this is a silent
|
|
13
|
+
* no-op. The public `ApiNamespace` signature is unchanged; this is a purely
|
|
14
|
+
* internal side effect.
|
|
15
|
+
*
|
|
16
|
+
* No `try/catch`: `BlocksBackend.create()` initializes the default compute
|
|
17
|
+
* before it imports the backend module that constructs any `ApiNamespace`, so
|
|
18
|
+
* the `compute` getter always resolves at this call site. If it ever threw
|
|
19
|
+
* here it would signal a genuine lifecycle violation (an `ApiNamespace` built
|
|
20
|
+
* before `create()` resolved), which should surface rather than be swallowed.
|
|
21
|
+
*/
|
|
22
|
+
function recordNamespaceOnCompute(scope, name) {
|
|
23
|
+
// An API created without a Scope stays unrecorded and routes to the
|
|
24
|
+
// default compute.
|
|
25
|
+
if (!scope || typeof scope !== 'object')
|
|
26
|
+
return;
|
|
27
|
+
const compute = scope.compute;
|
|
28
|
+
// Guard against duplicates so `namespaces` stays a set of names: a namespace
|
|
29
|
+
// could otherwise be recorded twice (e.g. a re-imported module during synth).
|
|
30
|
+
if (compute && Array.isArray(compute.namespaces) && !compute.namespaces.includes(name)) {
|
|
31
|
+
compute.namespaces.push(name);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
5
34
|
export const ApiNamespace = class ApiNamespace {
|
|
6
35
|
constructor(scope, name, handler) {
|
|
7
36
|
handler[API_NAMESPACE_MARKER] = name;
|
|
37
|
+
// Record the namespace → compute association for per-compute routing.
|
|
38
|
+
// No-op outside CDK synth. Signature and returned handler are unchanged.
|
|
39
|
+
recordNamespaceOnCompute(scope, name);
|
|
8
40
|
return handler;
|
|
9
41
|
}
|
|
10
42
|
};
|
package/dist/api.test.js
CHANGED
|
@@ -17,3 +17,25 @@ test('Different ApiNamespace names should not collide', () => {
|
|
|
17
17
|
assert.strictEqual(api1[API_NAMESPACE_MARKER], 'api1');
|
|
18
18
|
assert.strictEqual(api2[API_NAMESPACE_MARKER], 'api2');
|
|
19
19
|
});
|
|
20
|
+
// ApiNamespace records its name on the scope's resolved compute (CDK
|
|
21
|
+
// synth), while staying a silent no-op where no compute is resolvable.
|
|
22
|
+
test('ApiNamespace records its name on a resolvable compute', () => {
|
|
23
|
+
const compute = { namespaces: [] };
|
|
24
|
+
new ApiNamespace({ id: 'app', compute }, 'myapi', () => ({ ping: () => 'ok' }));
|
|
25
|
+
assert.deepStrictEqual(compute.namespaces, ['myapi']);
|
|
26
|
+
});
|
|
27
|
+
test('ApiNamespace is a no-op recorder when the scope has no compute', () => {
|
|
28
|
+
// The common Scope (mock/runtime) has no `compute`; recording must not throw
|
|
29
|
+
// and the handler is still tagged and returned unchanged.
|
|
30
|
+
const handler = new ApiNamespace(new Scope('no-compute'), 'plainapi', () => ({ x: () => 1 }));
|
|
31
|
+
assert.strictEqual(handler[API_NAMESPACE_MARKER], 'plainapi');
|
|
32
|
+
});
|
|
33
|
+
test('ApiNamespace records a namespace at most once per compute', () => {
|
|
34
|
+
// `namespaces` is a set of names: recording the same name twice (e.g. a
|
|
35
|
+
// re-imported module during synth) must not append a duplicate.
|
|
36
|
+
const compute = { namespaces: [] };
|
|
37
|
+
const scopeLike = { id: 'app', compute };
|
|
38
|
+
new ApiNamespace(scopeLike, 'dup', () => ({ ping: () => 'ok' }));
|
|
39
|
+
new ApiNamespace(scopeLike, 'dup', () => ({ ping: () => 'ok' }));
|
|
40
|
+
assert.deepStrictEqual(compute.namespaces, ['dup']);
|
|
41
|
+
});
|
package/dist/bb-utils.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ export { getMockDataDir } from './common/mock-data.js';
|
|
|
8
8
|
export { API_NAMESPACE_MARKER } from './api.js';
|
|
9
9
|
export { EventSourceMapping } from './lambda-handler.js';
|
|
10
10
|
export { constantTimeEquals } from './common/crypto.js';
|
|
11
|
+
export { sanitizeConfigKey } from './common/config.js';
|
|
11
12
|
//# sourceMappingURL=bb-utils.d.ts.map
|
package/dist/bb-utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bb-utils.d.ts","sourceRoot":"","sources":["../src/bb-utils.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"bb-utils.d.ts","sourceRoot":"","sources":["../src/bb-utils.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC"}
|
package/dist/bb-utils.js
CHANGED
|
@@ -10,3 +10,6 @@ export { getMockDataDir } from './common/mock-data.js';
|
|
|
10
10
|
export { API_NAMESPACE_MARKER } from './api.js';
|
|
11
11
|
export { EventSourceMapping } from './lambda-handler.js';
|
|
12
12
|
export { constantTimeEquals } from './common/crypto.js';
|
|
13
|
+
// Defined in the config module (it owns the config-key contract), re-exported
|
|
14
|
+
// here so BB authors get it alongside the other BB utilities.
|
|
15
|
+
export { sanitizeConfigKey } from './common/config.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type * as cdk from 'aws-cdk-lib';
|
|
2
|
+
import { CfnAccount } from 'aws-cdk-lib/aws-apigateway';
|
|
3
|
+
/**
|
|
4
|
+
* Ensure the account-level API Gateway CloudWatch Logs role exists on `stack`,
|
|
5
|
+
* creating it at most once per stack.
|
|
6
|
+
*
|
|
7
|
+
* API Gateway access logging — for both REST (v1) and WebSocket (v2) stages —
|
|
8
|
+
* requires an `AWS::ApiGateway::Account` whose `cloudWatchRoleArn` points at a
|
|
9
|
+
* role trusted by `apigateway.amazonaws.com` and carrying
|
|
10
|
+
* `AmazonAPIGatewayPushToCloudWatchLogs`. Nothing else in a Blocks stack
|
|
11
|
+
* provisions it, so a clean-account first deploy of any access-logging stage
|
|
12
|
+
* otherwise fails at CreateStage with "CloudWatch Logs role ARN must be set in
|
|
13
|
+
* account settings to enable logging".
|
|
14
|
+
*
|
|
15
|
+
* Callers should add their stage's dependency on the returned account so the
|
|
16
|
+
* account setting is applied before the stage is created.
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ Single-Blocks-stack-per-region assumption. `AWS::ApiGateway::Account` is an
|
|
19
|
+
* account/region-level singleton (one effective `cloudWatchRoleArn` per region),
|
|
20
|
+
* but this provisions one per Blocks stack. If two Blocks stacks in the same
|
|
21
|
+
* account+region both enable access logging:
|
|
22
|
+
* - on deploy, the later stack's role wins (overwrites the account setting); and
|
|
23
|
+
* - on teardown of that later stack, its role is deleted and the account
|
|
24
|
+
* setting is left pointing at a now-deleted role — silently breaking access
|
|
25
|
+
* logging (or blocking `CreateStage`) for the surviving stack until it
|
|
26
|
+
* redeploys.
|
|
27
|
+
* Enabling `accessLogging` is therefore safe for one Blocks stack per region.
|
|
28
|
+
* Multi-stack support (a shared/imported account) is deferred; if you run
|
|
29
|
+
* multiple Blocks stacks per region, manage the account role out-of-band and
|
|
30
|
+
* leave `accessLogging` to a single owner.
|
|
31
|
+
*/
|
|
32
|
+
export declare function ensureApiGatewayAccount(stack: cdk.Stack): CfnAccount;
|
|
33
|
+
//# sourceMappingURL=apigateway-account.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apigateway-account.d.ts","sourceRoot":"","sources":["../../src/cdk/apigateway-account.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,GAAG,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAaxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,UAAU,CAmBpE"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { CfnAccount } from 'aws-cdk-lib/aws-apigateway';
|
|
4
|
+
import { ManagedPolicy, Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam';
|
|
5
|
+
/**
|
|
6
|
+
* Global registry key for the per-stack API Gateway CloudWatch Logs account
|
|
7
|
+
* resource. Deliberately a `Symbol.for(...)` string key so the SAME symbol is
|
|
8
|
+
* shared across packages (core, hosting) — each package can carry its own copy
|
|
9
|
+
* of {@link ensureApiGatewayAccount} yet still find and reuse the one account a
|
|
10
|
+
* sibling package already created on the stack, instead of emitting a second
|
|
11
|
+
* `AWS::ApiGateway::Account` (which is account/region-level and would clash).
|
|
12
|
+
*/
|
|
13
|
+
const ACCOUNT_KEY = Symbol.for('BLOCKS_APIGATEWAY_ACCOUNT');
|
|
14
|
+
/**
|
|
15
|
+
* Ensure the account-level API Gateway CloudWatch Logs role exists on `stack`,
|
|
16
|
+
* creating it at most once per stack.
|
|
17
|
+
*
|
|
18
|
+
* API Gateway access logging — for both REST (v1) and WebSocket (v2) stages —
|
|
19
|
+
* requires an `AWS::ApiGateway::Account` whose `cloudWatchRoleArn` points at a
|
|
20
|
+
* role trusted by `apigateway.amazonaws.com` and carrying
|
|
21
|
+
* `AmazonAPIGatewayPushToCloudWatchLogs`. Nothing else in a Blocks stack
|
|
22
|
+
* provisions it, so a clean-account first deploy of any access-logging stage
|
|
23
|
+
* otherwise fails at CreateStage with "CloudWatch Logs role ARN must be set in
|
|
24
|
+
* account settings to enable logging".
|
|
25
|
+
*
|
|
26
|
+
* Callers should add their stage's dependency on the returned account so the
|
|
27
|
+
* account setting is applied before the stage is created.
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ Single-Blocks-stack-per-region assumption. `AWS::ApiGateway::Account` is an
|
|
30
|
+
* account/region-level singleton (one effective `cloudWatchRoleArn` per region),
|
|
31
|
+
* but this provisions one per Blocks stack. If two Blocks stacks in the same
|
|
32
|
+
* account+region both enable access logging:
|
|
33
|
+
* - on deploy, the later stack's role wins (overwrites the account setting); and
|
|
34
|
+
* - on teardown of that later stack, its role is deleted and the account
|
|
35
|
+
* setting is left pointing at a now-deleted role — silently breaking access
|
|
36
|
+
* logging (or blocking `CreateStage`) for the surviving stack until it
|
|
37
|
+
* redeploys.
|
|
38
|
+
* Enabling `accessLogging` is therefore safe for one Blocks stack per region.
|
|
39
|
+
* Multi-stack support (a shared/imported account) is deferred; if you run
|
|
40
|
+
* multiple Blocks stacks per region, manage the account role out-of-band and
|
|
41
|
+
* leave `accessLogging` to a single owner.
|
|
42
|
+
*/
|
|
43
|
+
export function ensureApiGatewayAccount(stack) {
|
|
44
|
+
const existing = stack[ACCOUNT_KEY];
|
|
45
|
+
if (existing)
|
|
46
|
+
return existing;
|
|
47
|
+
const role = new Role(stack, 'BlocksApiGatewayCloudWatchRole', {
|
|
48
|
+
assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
|
|
49
|
+
managedPolicies: [
|
|
50
|
+
ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonAPIGatewayPushToCloudWatchLogs'),
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
// `cloudWatchRoleArn: role.roleArn` already gives CDK an implicit dependency
|
|
54
|
+
// on the role, so no explicit node.addDependency is needed.
|
|
55
|
+
const account = new CfnAccount(stack, 'BlocksApiGatewayAccount', {
|
|
56
|
+
cloudWatchRoleArn: role.roleArn,
|
|
57
|
+
});
|
|
58
|
+
stack[ACCOUNT_KEY] = account;
|
|
59
|
+
return account;
|
|
60
|
+
}
|
|
@@ -70,6 +70,12 @@ export declare function setupBlocksInfra(scope: Construct, props: BlocksBackendP
|
|
|
70
70
|
*/
|
|
71
71
|
export declare class BlocksBackend extends Construct {
|
|
72
72
|
readonly backendHandlerPath: string;
|
|
73
|
+
/**
|
|
74
|
+
* Path to the app's backend module (`props.backendCDKPath`). Exposed so Building Blocks that
|
|
75
|
+
* co-bundle the backend at synth (e.g. the Agent BB's AgentCore Runtime) can discover it via
|
|
76
|
+
* `globalThis.CURRENT_BLOCKS_STACK.backendModulePath`.
|
|
77
|
+
*/
|
|
78
|
+
readonly backendModulePath: string;
|
|
73
79
|
/** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
|
|
74
80
|
readonly executionRole: iam.IRole;
|
|
75
81
|
/** Infrastructure defaults for Building Blocks created under this backend. */
|
|
@@ -82,6 +88,8 @@ export declare class BlocksBackend extends Construct {
|
|
|
82
88
|
get gateway(): apigateway.RestApi;
|
|
83
89
|
/** The default compute's RPC endpoint URL. To be removed once consumers move to the multi-compute model. */
|
|
84
90
|
get apiUrl(): string;
|
|
91
|
+
/** The default compute's handler CloudWatch log group. `bb-logger` reconfigures its retention. */
|
|
92
|
+
get handlerLogGroup(): cdk.aws_logs.ILogGroup;
|
|
85
93
|
private requireDefaultCompute;
|
|
86
94
|
/**
|
|
87
95
|
* The fullId used by child Scopes to compute their env var names,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blocks-backend.d.ts","sourceRoot":"","sources":["../../src/cdk/blocks-backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,KAAK,UAAU,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"blocks-backend.d.ts","sourceRoot":"","sources":["../../src/cdk/blocks-backend.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,KAAK,KAAK,UAAU,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,KAAK,EAAE,qBAAqB,EAAuB,MAAM,sCAAsC,CAAC;AAEvG;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAkB/C;AAED;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,QAAU,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IAChE,8EAA8E;IAC9E,qBAAqB,EAAE,qBAAqB,CAAC;CAC9C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,CAAC,EAAE,MAAM;;EAyFxF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,aAAc,SAAQ,SAAS;IAC1C,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAC3C;;;;OAIG;IACH,SAAgB,iBAAiB,EAAE,MAAM,CAAC;IAC1C,yFAAyF;IACzF,SAAgB,aAAa,EAAE,GAAG,CAAC,KAAK,CAAC;IACzC,8EAA8E;IAC9E,SAAgB,QAAQ,EAAE,cAAc,CAAC;IACzC,iGAAiG;IACjG,eAAe,CAAC,EAAE,OAAO,CAAC;IAE1B,2GAA2G;IAC3G,IAAI,OAAO,IAAI,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAElD;IACD,gHAAgH;IAChH,IAAI,OAAO,IAAI,UAAU,CAAC,OAAO,CAEhC;IACD,4GAA4G;IAC5G,IAAI,MAAM,IAAI,MAAM,CAEnB;IACD,kGAAkG;IAClG,IAAI,eAAe,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAE5C;IAED,OAAO,CAAC,qBAAqB;IAO7B;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,MAAM,IAAI,MAAM,CAUnB;IAED,OAAO;WAoBM,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB;CA6BhF"}
|
|
@@ -8,6 +8,7 @@ import { pathToFileURL } from 'node:url';
|
|
|
8
8
|
import { addBlocksStackMetadata } from './stack-metadata.js';
|
|
9
9
|
import { finalizeConfigRegistry, registerConfig } from './config-registry.js';
|
|
10
10
|
import { registerBuiltinRoutes } from '../builtin-routes.js';
|
|
11
|
+
import { getComputes } from './compute/compute-registry.js';
|
|
11
12
|
/**
|
|
12
13
|
* Validate that the Node.js process was started with `--conditions=cdk`.
|
|
13
14
|
*
|
|
@@ -64,9 +65,10 @@ export function setupBlocksInfra(scope, props, id) {
|
|
|
64
65
|
// so permissions would quietly vanish. If a bring-your-own-role option is ever
|
|
65
66
|
// added, it must resolve to a mutable role (`{ mutable: true }`).
|
|
66
67
|
const executionRole = new iam.Role(scope, 'BlocksRole', {
|
|
67
|
-
// CompositePrincipal (rather than a bare ServicePrincipal) so
|
|
68
|
-
// compute
|
|
69
|
-
// (e.g.
|
|
68
|
+
// CompositePrincipal (rather than a bare ServicePrincipal) so a Building Block
|
|
69
|
+
// whose compute runs AS this shared role can add its own trust principal here
|
|
70
|
+
// (e.g. the Agent BB adds bedrock-agentcore in its CDK construct) — core stays
|
|
71
|
+
// agnostic and only Lambda is trusted by default.
|
|
70
72
|
assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('lambda.amazonaws.com')),
|
|
71
73
|
managedPolicies: [
|
|
72
74
|
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
|
|
@@ -140,6 +142,12 @@ export function setupBlocksInfra(scope, props, id) {
|
|
|
140
142
|
*/
|
|
141
143
|
export class BlocksBackend extends Construct {
|
|
142
144
|
backendHandlerPath;
|
|
145
|
+
/**
|
|
146
|
+
* Path to the app's backend module (`props.backendCDKPath`). Exposed so Building Blocks that
|
|
147
|
+
* co-bundle the backend at synth (e.g. the Agent BB's AgentCore Runtime) can discover it via
|
|
148
|
+
* `globalThis.CURRENT_BLOCKS_STACK.backendModulePath`.
|
|
149
|
+
*/
|
|
150
|
+
backendModulePath;
|
|
143
151
|
/** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
|
|
144
152
|
executionRole;
|
|
145
153
|
/** Infrastructure defaults for Building Blocks created under this backend. */
|
|
@@ -158,6 +166,10 @@ export class BlocksBackend extends Construct {
|
|
|
158
166
|
get apiUrl() {
|
|
159
167
|
return this.requireDefaultCompute().apiUrl;
|
|
160
168
|
}
|
|
169
|
+
/** The default compute's handler CloudWatch log group. `bb-logger` reconfigures its retention. */
|
|
170
|
+
get handlerLogGroup() {
|
|
171
|
+
return this.requireDefaultCompute().logGroup;
|
|
172
|
+
}
|
|
161
173
|
requireDefaultCompute() {
|
|
162
174
|
if (!this._defaultCompute) {
|
|
163
175
|
throw new Error('Blocks backend not fully initialized — access .handler/.gateway/.apiUrl after BlocksBackend.create() resolves.');
|
|
@@ -197,6 +209,7 @@ export class BlocksBackend extends Construct {
|
|
|
197
209
|
constructor(scope, id, props) {
|
|
198
210
|
super(scope, id);
|
|
199
211
|
this.backendHandlerPath = props.backendHandlerPath;
|
|
212
|
+
this.backendModulePath = props.backendCDKPath;
|
|
200
213
|
// Expose self to Building Blocks at CDK time
|
|
201
214
|
globalThis.CURRENT_BLOCKS_STACK = this;
|
|
202
215
|
// Store defaults on the backend (not the stack) so several BlocksBackends
|
|
@@ -233,7 +246,7 @@ export class BlocksBackend extends Construct {
|
|
|
233
246
|
}
|
|
234
247
|
addBlocksStackMetadata(cdk.Stack.of(backend));
|
|
235
248
|
// Finalize BB config → S3 (after all BBs have registered their config)
|
|
236
|
-
finalizeConfigRegistry(backend, backend.
|
|
249
|
+
finalizeConfigRegistry(backend, backend.executionRole, getComputes(backend));
|
|
237
250
|
return backend;
|
|
238
251
|
}
|
|
239
252
|
}
|
|
@@ -25,13 +25,18 @@ class StubLambdaCompute extends Compute {
|
|
|
25
25
|
fn;
|
|
26
26
|
apiGateway;
|
|
27
27
|
apiUrl;
|
|
28
|
+
logGroup;
|
|
28
29
|
constructor(scope, id) {
|
|
29
30
|
super(id, { parent: scope });
|
|
31
|
+
this.logGroup = new cdk.aws_logs.LogGroup(this, 'HandlerLogGroup', {
|
|
32
|
+
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
33
|
+
});
|
|
30
34
|
this.fn = new lambda.NodejsFunction(this, 'Handler', {
|
|
31
35
|
entry: this.backendHandlerPath,
|
|
32
36
|
runtime: cdk.aws_lambda.Runtime.NODEJS_22_X,
|
|
33
37
|
handler: 'handler',
|
|
34
38
|
role: this.executionRole,
|
|
39
|
+
logGroup: this.logGroup,
|
|
35
40
|
environment: { BLOCKS_STACK_NAME: this.backendStackName },
|
|
36
41
|
bundling: { minify: true, esbuildArgs: { '--conditions': 'aws-runtime' } },
|
|
37
42
|
});
|
|
@@ -123,11 +128,23 @@ describe('shared execution role', () => {
|
|
|
123
128
|
Principal: { Service: 'lambda.amazonaws.com' },
|
|
124
129
|
});
|
|
125
130
|
assert.ok(JSON.stringify(blocksRole.Properties.ManagedPolicyArns ?? []).includes('AWSLambdaBasicExecutionRole'), 'BlocksRole should attach AWSLambdaBasicExecutionRole');
|
|
131
|
+
// Core is BB-agnostic: with no Building Block that runs AS the shared role, the trust policy
|
|
132
|
+
// must NOT allow any compute principal beyond Lambda (e.g. bedrock-agentcore is added by the
|
|
133
|
+
// Agent BB's own construct, not here — see packages/bb-agent).
|
|
134
|
+
assert.ok(!JSON.stringify(blocksRole.Properties.AssumeRolePolicyDocument).includes('bedrock-agentcore'), 'core must not trust bedrock-agentcore by default (no agent → no AgentCore trust)');
|
|
126
135
|
// The Blocks handler references the shared role, not an auto-generated one.
|
|
127
136
|
template.hasResourceProperties('AWS::Lambda::Function', {
|
|
128
137
|
Role: { 'Fn::GetAtt': [blocksRoleId, 'Arn'] },
|
|
129
138
|
});
|
|
130
139
|
});
|
|
140
|
+
test('exposes backendModulePath (props.backendCDKPath) for co-bundling BBs', async () => {
|
|
141
|
+
const app = new cdk.App();
|
|
142
|
+
const parent = new cdk.Stack(app, 'BackendPathStack');
|
|
143
|
+
const backend = await makeBackend(parent, 'Blocks', sideEffectBackendPath);
|
|
144
|
+
// Building Blocks that co-bundle the app backend at synth time (e.g. the Agent BB's AgentCore
|
|
145
|
+
// Runtime) discover it via globalThis.CURRENT_BLOCKS_STACK.backendModulePath.
|
|
146
|
+
assert.strictEqual(backend.backendModulePath, sideEffectBackendPath);
|
|
147
|
+
});
|
|
131
148
|
test('a nested block resolves executionRole via the construct-tree walk', async () => {
|
|
132
149
|
const app = new cdk.App();
|
|
133
150
|
const parent = new cdk.Stack(app, 'RoleResolveStack');
|
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import { RemovalPolicy } from 'aws-cdk-lib';
|
|
2
|
+
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
|
|
3
|
+
/**
|
|
4
|
+
* Request-rate limits applied to an API Gateway stage. On a REST API these are
|
|
5
|
+
* requests/second; on a WebSocket API the unit is messages/second across the
|
|
6
|
+
* connection. `rateLimit` is the steady-state ceiling and `burstLimit` the
|
|
7
|
+
* token-bucket size for short spikes.
|
|
8
|
+
*/
|
|
9
|
+
export interface BlocksThrottling {
|
|
10
|
+
/** Steady-state request (or WebSocket message) rate ceiling, per second. */
|
|
11
|
+
rateLimit: number;
|
|
12
|
+
/** Token-bucket burst size for short spikes above `rateLimit`. */
|
|
13
|
+
burstLimit: number;
|
|
14
|
+
}
|
|
2
15
|
/**
|
|
3
16
|
* Default values for every Amazon-authored Building Block created within a
|
|
4
17
|
* `BlocksStack` or `BlocksBackend`. A per-block option always overrides the
|
|
@@ -42,6 +55,40 @@ export interface BlocksDefaults {
|
|
|
42
55
|
pointInTimeRecovery: boolean | {
|
|
43
56
|
retentionDays: number;
|
|
44
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* How long CloudWatch Logs keeps log events written by Blocks-managed log
|
|
60
|
+
* groups (the core handler Lambda, migration/GSI Lambdas, hosting compute,
|
|
61
|
+
* `bb-logger`, and API Gateway access logs) before expiring them. Without a
|
|
62
|
+
* retention set, AWS keeps log events forever, which grows cost unbounded —
|
|
63
|
+
* every Blocks-managed log group reads this default so retention is applied
|
|
64
|
+
* consistently.
|
|
65
|
+
*/
|
|
66
|
+
logRetention: RetentionDays;
|
|
67
|
+
/**
|
|
68
|
+
* Request-rate limits applied to every Blocks-managed API Gateway stage: the
|
|
69
|
+
* core REST API, the SSR/hosting REST API, and the `bb-realtime` WebSocket
|
|
70
|
+
* stage. Protects the backend from runaway clients and caps blast radius.
|
|
71
|
+
* The sandbox preset caps tighter (200/400) than production (1000/2000) so a
|
|
72
|
+
* disposable stack is well-protected without throttling real production
|
|
73
|
+
* traffic. See {@link BlocksThrottling}.
|
|
74
|
+
*/
|
|
75
|
+
throttling: BlocksThrottling;
|
|
76
|
+
/**
|
|
77
|
+
* Whether to emit structured JSON access logs from every Blocks-managed API
|
|
78
|
+
* Gateway stage to a dedicated CloudWatch log group (retention follows
|
|
79
|
+
* {@link logRetention}).
|
|
80
|
+
*
|
|
81
|
+
* **Off by default in both presets** — opt in with a per-stack override
|
|
82
|
+
* (`defaults: { ...BlocksPresets.production, accessLogging: true }`). It is
|
|
83
|
+
* off by default (rather than on for production) because enabling it
|
|
84
|
+
* provisions the account-level API Gateway CloudWatch Logs role, which is an
|
|
85
|
+
* account/region-level singleton: a second Blocks stack in the same
|
|
86
|
+
* account+region that also enables access logging can repoint that role on
|
|
87
|
+
* deploy or, on teardown, leave the survivor's access logging broken.
|
|
88
|
+
* Enabling it is therefore safe for **one Blocks stack per region** — see
|
|
89
|
+
* `ensureApiGatewayAccount` for the full multi-stack teardown caveat.
|
|
90
|
+
*/
|
|
91
|
+
accessLogging: boolean;
|
|
45
92
|
}
|
|
46
93
|
/**
|
|
47
94
|
* Prepared starting points for {@link BlocksDefaults}. Pick one and override
|
|
@@ -58,13 +105,30 @@ export declare const BlocksPresets: {
|
|
|
58
105
|
deletionProtection: false;
|
|
59
106
|
allowedOrigins: string[];
|
|
60
107
|
pointInTimeRecovery: false;
|
|
108
|
+
logRetention: RetentionDays.ONE_WEEK;
|
|
109
|
+
throttling: {
|
|
110
|
+
rateLimit: number;
|
|
111
|
+
burstLimit: number;
|
|
112
|
+
};
|
|
113
|
+
accessLogging: false;
|
|
61
114
|
};
|
|
62
|
-
/**
|
|
115
|
+
/**
|
|
116
|
+
* Durable, protected posture for permanent deployments. A higher throttle
|
|
117
|
+
* ceiling (1000/2000) than sandbox so the default doesn't 429 real
|
|
118
|
+
* production traffic — raise it via a per-stack `throttling` override for
|
|
119
|
+
* higher-volume APIs.
|
|
120
|
+
*/
|
|
63
121
|
production: {
|
|
64
122
|
removalPolicy: RemovalPolicy.RETAIN;
|
|
65
123
|
deletionProtection: true;
|
|
66
124
|
allowedOrigins: never[];
|
|
67
125
|
pointInTimeRecovery: true;
|
|
126
|
+
logRetention: RetentionDays.ONE_YEAR;
|
|
127
|
+
throttling: {
|
|
128
|
+
rateLimit: number;
|
|
129
|
+
burstLimit: number;
|
|
130
|
+
};
|
|
131
|
+
accessLogging: false;
|
|
68
132
|
};
|
|
69
133
|
};
|
|
70
134
|
//# sourceMappingURL=blocks-defaults.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blocks-defaults.d.ts","sourceRoot":"","sources":["../../src/cdk/blocks-defaults.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"blocks-defaults.d.ts","sourceRoot":"","sources":["../../src/cdk/blocks-defaults.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAChC,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC9B;;;;;OAKG;IACH,aAAa,EAAE,aAAa,CAAC;IAE7B;;;;OAIG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAE5B;;;;;OAKG;IACH,cAAc,EAAE,MAAM,EAAE,CAAC;IAEzB;;;;;;;;;;;OAWG;IACH,mBAAmB,EAAE,OAAO,GAAG;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IAEzD;;;;;;;OAOG;IACH,YAAY,EAAE,aAAa,CAAC;IAE5B;;;;;;;OAOG;IACH,UAAU,EAAE,gBAAgB,CAAC;IAE7B;;;;;;;;;;;;;;OAcG;IACH,aAAa,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa;IACzB,qFAAqF;;;;;;;;;;;;;IAUrF;;;;;OAKG;;;;;;;;;;;;;CAcsC,CAAC"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import { RemovalPolicy } from 'aws-cdk-lib';
|
|
4
|
+
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
|
|
4
5
|
/**
|
|
5
6
|
* Prepared starting points for {@link BlocksDefaults}. Pick one and override
|
|
6
7
|
* individual fields with a spread:
|
|
@@ -16,12 +17,27 @@ export const BlocksPresets = {
|
|
|
16
17
|
deletionProtection: false,
|
|
17
18
|
allowedOrigins: ['^https?://(localhost|127\\.0\\.0\\.1)(:\\d+)?$'],
|
|
18
19
|
pointInTimeRecovery: false,
|
|
20
|
+
logRetention: RetentionDays.ONE_WEEK,
|
|
21
|
+
throttling: { rateLimit: 200, burstLimit: 400 },
|
|
22
|
+
accessLogging: false,
|
|
19
23
|
},
|
|
20
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* Durable, protected posture for permanent deployments. A higher throttle
|
|
26
|
+
* ceiling (1000/2000) than sandbox so the default doesn't 429 real
|
|
27
|
+
* production traffic — raise it via a per-stack `throttling` override for
|
|
28
|
+
* higher-volume APIs.
|
|
29
|
+
*/
|
|
21
30
|
production: {
|
|
22
31
|
removalPolicy: RemovalPolicy.RETAIN,
|
|
23
32
|
deletionProtection: true,
|
|
24
33
|
allowedOrigins: [],
|
|
25
34
|
pointInTimeRecovery: true,
|
|
35
|
+
logRetention: RetentionDays.ONE_YEAR,
|
|
36
|
+
throttling: { rateLimit: 1000, burstLimit: 2000 },
|
|
37
|
+
// Off by default even in production: enabling access logging mutates the
|
|
38
|
+
// account/region-level API Gateway CloudWatch role (a singleton). Opt in
|
|
39
|
+
// per stack once you've confirmed a single Blocks stack owns it in the
|
|
40
|
+
// region — see the `accessLogging` field doc.
|
|
41
|
+
accessLogging: false,
|
|
26
42
|
},
|
|
27
43
|
};
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import { test, describe } from 'node:test';
|
|
10
10
|
import assert from 'node:assert';
|
|
11
11
|
import { RemovalPolicy } from 'aws-cdk-lib';
|
|
12
|
+
import { RetentionDays } from 'aws-cdk-lib/aws-logs';
|
|
12
13
|
import { BlocksPresets } from './blocks-defaults.js';
|
|
13
14
|
describe('BlocksPresets', () => {
|
|
14
15
|
test('sandbox is disposable: DESTROY + deletion protection off + no PITR', () => {
|
|
@@ -21,4 +22,20 @@ describe('BlocksPresets', () => {
|
|
|
21
22
|
assert.strictEqual(BlocksPresets.production.deletionProtection, true);
|
|
22
23
|
assert.strictEqual(BlocksPresets.production.pointInTimeRecovery, true);
|
|
23
24
|
});
|
|
25
|
+
test('sandbox keeps logs briefly', () => {
|
|
26
|
+
assert.strictEqual(BlocksPresets.sandbox.logRetention, RetentionDays.ONE_WEEK);
|
|
27
|
+
});
|
|
28
|
+
test('production keeps logs a year', () => {
|
|
29
|
+
assert.strictEqual(BlocksPresets.production.logRetention, RetentionDays.ONE_YEAR);
|
|
30
|
+
});
|
|
31
|
+
test('access logging is off by default in BOTH presets (opt-in; mutates an account singleton)', () => {
|
|
32
|
+
assert.strictEqual(BlocksPresets.sandbox.accessLogging, false);
|
|
33
|
+
assert.strictEqual(BlocksPresets.production.accessLogging, false);
|
|
34
|
+
});
|
|
35
|
+
test('sandbox caps tighter (200/400) than production (1000/2000)', () => {
|
|
36
|
+
assert.strictEqual(BlocksPresets.sandbox.throttling.rateLimit, 200);
|
|
37
|
+
assert.strictEqual(BlocksPresets.sandbox.throttling.burstLimit, 400);
|
|
38
|
+
assert.strictEqual(BlocksPresets.production.throttling.rateLimit, 1000);
|
|
39
|
+
assert.strictEqual(BlocksPresets.production.throttling.burstLimit, 2000);
|
|
40
|
+
});
|
|
24
41
|
});
|
|
@@ -10,6 +10,7 @@ import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
|
10
10
|
import { BLOCKS_RPC_PREFIX } from '../constants.js';
|
|
11
11
|
import { BlocksBackend } from './blocks-backend.js';
|
|
12
12
|
import { Compute } from './compute/compute.js';
|
|
13
|
+
import { getComputes } from './compute/compute-registry.js';
|
|
13
14
|
import { BlocksStack, BlocksPresets, Scope } from './index.js';
|
|
14
15
|
// A real app gets its default compute from @aws-blocks/bb-lambda-compute (via
|
|
15
16
|
// @aws-blocks/blocks), which core's own tests can't depend on. Use an
|
|
@@ -22,13 +23,18 @@ class StubLambdaCompute extends Compute {
|
|
|
22
23
|
fn;
|
|
23
24
|
apiGateway;
|
|
24
25
|
apiUrl;
|
|
26
|
+
logGroup;
|
|
25
27
|
constructor(scope, id) {
|
|
26
28
|
super(id, { parent: scope });
|
|
29
|
+
this.logGroup = new cdk.aws_logs.LogGroup(this, 'HandlerLogGroup', {
|
|
30
|
+
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
31
|
+
});
|
|
27
32
|
this.fn = new lambda.NodejsFunction(this, 'Handler', {
|
|
28
33
|
entry: this.backendHandlerPath,
|
|
29
34
|
runtime: cdk.aws_lambda.Runtime.NODEJS_22_X,
|
|
30
35
|
handler: 'handler',
|
|
31
36
|
role: this.executionRole,
|
|
37
|
+
logGroup: this.logGroup,
|
|
32
38
|
environment: { BLOCKS_STACK_NAME: this.backendStackName },
|
|
33
39
|
bundling: { minify: true, esbuildArgs: { '--conditions': 'aws-runtime' } },
|
|
34
40
|
});
|
|
@@ -130,6 +136,21 @@ describe('root is bound to the owning stack (multi-stack synth)', () => {
|
|
|
130
136
|
assert.strictEqual(blockA.backendStackName, 'RootBindingA', 'blockA derives its own stack name');
|
|
131
137
|
assert.strictEqual(blockB.backendStackName, 'RootBindingB', 'blockB derives its own stack name');
|
|
132
138
|
});
|
|
139
|
+
test('each stack owns an isolated compute registry (no cross-stack bleed)', async () => {
|
|
140
|
+
// Computes self-register on their owning stack (keyed per stack, not a
|
|
141
|
+
// process-global list), so a multi-stack synth keeps each stack's computes
|
|
142
|
+
// separate — finalize steps for one stack never see another's compute.
|
|
143
|
+
const app = new cdk.App();
|
|
144
|
+
const stackA = await makeStack(app, 'ComputeRegistryA', sideEffectBackendPath);
|
|
145
|
+
const stackB = await makeStack(app, 'ComputeRegistryB', sideEffectBackendPath);
|
|
146
|
+
const computesA = getComputes(stackA);
|
|
147
|
+
const computesB = getComputes(stackB);
|
|
148
|
+
assert.strictEqual(computesA.length, 1, 'stackA registered exactly its default compute');
|
|
149
|
+
assert.strictEqual(computesB.length, 1, 'stackB registered exactly its default compute');
|
|
150
|
+
assert.strictEqual(computesA[0], stackA._defaultCompute, 'stackA lists its own default');
|
|
151
|
+
assert.strictEqual(computesB[0], stackB._defaultCompute, 'stackB lists its own default');
|
|
152
|
+
assert.notStrictEqual(computesA[0], computesB[0], 'the two stacks hold distinct computes');
|
|
153
|
+
});
|
|
133
154
|
});
|
|
134
155
|
describe('assertCdkConditionActive', () => {
|
|
135
156
|
test('BlocksStack.create() throws when --conditions=cdk is missing', async () => {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Construct } from 'constructs';
|
|
2
|
+
import type { Compute } from './compute.js';
|
|
3
|
+
/**
|
|
4
|
+
* Register a compute on its owning stack. Called from the {@link Compute} base
|
|
5
|
+
* constructor, so every compute self-registers the moment it is constructed —
|
|
6
|
+
* the finalize steps then enumerate them without a separate discovery pass
|
|
7
|
+
* (mirrors how `registerConfig` accumulates config during the backend import).
|
|
8
|
+
*
|
|
9
|
+
* @param compute - The compute to register (used to locate its stack).
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerCompute(compute: Compute): void;
|
|
12
|
+
/**
|
|
13
|
+
* The computes registered on the stack that owns `scope`, in construction
|
|
14
|
+
* order. Returns an empty array before any compute is constructed.
|
|
15
|
+
*
|
|
16
|
+
* @param scope - Any construct in the stack (used to locate the stack).
|
|
17
|
+
*/
|
|
18
|
+
export declare function getComputes(scope: Construct): readonly Compute[];
|
|
19
|
+
//# sourceMappingURL=compute-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compute-registry.d.ts","sourceRoot":"","sources":["../../../src/cdk/compute/compute-registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAmB5C;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,OAAO,EAAE,CAEhE"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import * as cdk from 'aws-cdk-lib';
|
|
4
|
+
const REGISTRY_KEY = Symbol.for('BLOCKS_COMPUTE_REGISTRY');
|
|
5
|
+
/**
|
|
6
|
+
* Get or create the compute list for a given stack. The list is stored on the
|
|
7
|
+
* stack object (keyed by a Symbol), so each stack in a multi-stack synth gets
|
|
8
|
+
* its own — a compute never leaks into another stack's list. Mirrors the config
|
|
9
|
+
* registry (`config-registry.ts`), which scopes its state the same way.
|
|
10
|
+
*/
|
|
11
|
+
function getRegistry(stack) {
|
|
12
|
+
let list = stack[REGISTRY_KEY];
|
|
13
|
+
if (!list) {
|
|
14
|
+
list = [];
|
|
15
|
+
stack[REGISTRY_KEY] = list;
|
|
16
|
+
}
|
|
17
|
+
return list;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Register a compute on its owning stack. Called from the {@link Compute} base
|
|
21
|
+
* constructor, so every compute self-registers the moment it is constructed —
|
|
22
|
+
* the finalize steps then enumerate them without a separate discovery pass
|
|
23
|
+
* (mirrors how `registerConfig` accumulates config during the backend import).
|
|
24
|
+
*
|
|
25
|
+
* @param compute - The compute to register (used to locate its stack).
|
|
26
|
+
*/
|
|
27
|
+
export function registerCompute(compute) {
|
|
28
|
+
getRegistry(cdk.Stack.of(compute)).push(compute);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The computes registered on the stack that owns `scope`, in construction
|
|
32
|
+
* order. Returns an empty array before any compute is constructed.
|
|
33
|
+
*
|
|
34
|
+
* @param scope - Any construct in the stack (used to locate the stack).
|
|
35
|
+
*/
|
|
36
|
+
export function getComputes(scope) {
|
|
37
|
+
return getRegistry(cdk.Stack.of(scope));
|
|
38
|
+
}
|