@aws-blocks/core 0.3.0 → 0.5.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 +25 -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 +11 -0
- package/dist/cdk/blocks-backend.d.ts.map +1 -1
- package/dist/cdk/blocks-backend.js +72 -13
- package/dist/cdk/blocks-backend.test.js +88 -1
- package/dist/cdk/blocks-defaults.d.ts +76 -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 +44 -3
- 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 +82 -2
- package/dist/cdk/compute/compute.d.ts.map +1 -1
- package/dist/cdk/compute/compute.js +64 -2
- 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 +127 -0
- package/dist/cdk/dashboard-registry.d.ts +41 -0
- package/dist/cdk/dashboard-registry.d.ts.map +1 -0
- package/dist/cdk/dashboard-registry.js +61 -0
- package/dist/cdk/index.d.ts +90 -6
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +135 -13
- package/dist/cdk/internal.d.ts +3 -1
- package/dist/cdk/internal.d.ts.map +1 -1
- package/dist/cdk/internal.js +4 -1
- package/dist/cdk/tracer-registry.d.ts +31 -0
- package/dist/cdk/tracer-registry.d.ts.map +1 -0
- package/dist/cdk/tracer-registry.js +49 -0
- package/dist/cdk/vpc-requirements-registry.d.ts +33 -0
- package/dist/cdk/vpc-requirements-registry.d.ts.map +1 -0
- package/dist/cdk/vpc-requirements-registry.js +46 -0
- package/dist/cdk/vpc-types.d.ts +151 -0
- package/dist/cdk/vpc-types.d.ts.map +1 -0
- package/dist/cdk/vpc-types.js +3 -0
- package/dist/cdk/vpc.d.ts +59 -0
- package/dist/cdk/vpc.d.ts.map +1 -0
- package/dist/cdk/vpc.js +298 -0
- package/dist/cdk/vpc.test.d.ts +2 -0
- package/dist/cdk/vpc.test.d.ts.map +1 -0
- package/dist/cdk/vpc.test.js +285 -0
- 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 +21 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +25 -0
- package/dist/hosting.d.ts +9 -0
- package/dist/hosting.d.ts.map +1 -1
- package/dist/hosting.js +12 -1
- package/dist/hosting.test.js +63 -1
- package/dist/index.cdk.d.ts +3 -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 +61 -5
- package/dist/lambda-handler.test.js +191 -2
- package/dist/raw-route.d.ts +15 -1
- package/dist/raw-route.d.ts.map +1 -1
- package/dist/raw-route.js +96 -12
- package/dist/raw-route.test.js +332 -1
- package/dist/scripts/dev-server.d.ts.map +1 -1
- package/dist/scripts/dev-server.js +11 -0
- package/dist/scripts/extract-ts-types.d.ts.map +1 -1
- package/dist/scripts/extract-ts-types.js +107 -23
- package/dist/scripts/extract-ts-types.test.js +225 -26
- package/dist/scripts/generate-spec.d.ts.map +1 -1
- package/dist/scripts/generate-spec.js +14 -5
- package/dist/scripts/generate-spec.test.js +93 -0
- 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 +10 -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 +168 -60
- package/src/cdk/blocks-backend.ts +299 -227
- package/src/cdk/blocks-defaults.test.ts +21 -0
- package/src/cdk/blocks-defaults.ts +79 -1
- package/src/cdk/blocks-stack.test.ts +57 -13
- package/src/cdk/compute/compute-registry.ts +45 -0
- package/src/cdk/compute/compute.ts +114 -2
- package/src/cdk/compute/default-compute-factory.ts +1 -0
- package/src/cdk/config-registry.test.ts +149 -0
- package/src/cdk/config-registry.ts +92 -34
- package/src/cdk/dashboard-registry.ts +68 -0
- package/src/cdk/index.ts +427 -256
- package/src/cdk/internal.ts +6 -2
- package/src/cdk/tracer-registry.ts +54 -0
- package/src/cdk/vpc-requirements-registry.ts +63 -0
- package/src/cdk/vpc-types.ts +158 -0
- package/src/cdk/vpc.test.ts +348 -0
- package/src/cdk/vpc.ts +336 -0
- 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 +26 -0
- package/src/hosting.test.ts +87 -1
- package/src/hosting.ts +24 -1
- package/src/index.cdk.ts +11 -1
- package/src/index.ts +1 -1
- package/src/lambda-handler.test.ts +220 -2
- package/src/lambda-handler.ts +65 -4
- package/src/raw-route.test.ts +427 -1
- package/src/raw-route.ts +125 -12
- package/src/scripts/dev-server.ts +12 -1
- package/src/scripts/extract-ts-types.test.ts +228 -26
- package/src/scripts/extract-ts-types.ts +104 -20
- package/src/scripts/generate-spec.test.ts +101 -0
- package/src/scripts/generate-spec.ts +15 -5
- 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
|
@@ -76,6 +76,18 @@ The body is JSON-RPC 2.0:
|
|
|
76
76
|
- `params` is a POSITIONAL array of the method's arguments. A named object also works (its values are used in order).
|
|
77
77
|
- Errors come back as HTTP `200` with an `error` object in the body (per JSON-RPC), not as a non-2xx status.
|
|
78
78
|
|
|
79
|
+
Error responses use these JSON-RPC codes:
|
|
80
|
+
|
|
81
|
+
| Code | Meaning | When Blocks uses it |
|
|
82
|
+
|------|---------|---------------------|
|
|
83
|
+
| `-32700` | Parse error | Request body is not valid JSON. |
|
|
84
|
+
| `-32600` | Invalid Request | Body is not a valid Blocks JSON-RPC request, such as missing `jsonrpc: "2.0"` or a `method` that is not `namespace.method`. |
|
|
85
|
+
| `-32601` | Method not found | The requested namespace or method is not exported by the backend. |
|
|
86
|
+
| `-32602` | Invalid params | Reserved for JSON-RPC parameter validation failures. Application-level validation should throw an `ApiError` with the appropriate HTTP status instead. |
|
|
87
|
+
| `-32603` | Internal error | Reserved for generic JSON-RPC internal errors. Unhandled application errors currently use code `500`. |
|
|
88
|
+
|
|
89
|
+
Errors thrown with `ApiError` use the positive HTTP status as the JSON-RPC `error.code` (for example, `401`, `404`, or `409`) and include `error.data.name` when an error name is available. The typed client converts JSON-RPC errors back into `ApiError`.
|
|
90
|
+
|
|
79
91
|
Working example:
|
|
80
92
|
|
|
81
93
|
```bash
|
|
@@ -313,6 +325,19 @@ new Hosting(stack, 'Web', {
|
|
|
313
325
|
|
|
314
326
|
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
327
|
|
|
328
|
+
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):
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
new Hosting(stack, 'Web', {
|
|
332
|
+
root: join(__dirname, '..'),
|
|
333
|
+
api: blocksStack,
|
|
334
|
+
buildRetentionDays: 90,
|
|
335
|
+
});
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Core's top-level `buildRetentionDays` maps to the L3 construct's `storage.buildRetentionDays`.
|
|
339
|
+
|
|
340
|
+
|
|
316
341
|
## Building Blocks
|
|
317
342
|
|
|
318
343
|
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,10 @@ 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. Its retention comes from
|
|
92
|
+
* the compute's `logRetention` (falling back to `defaults.logRetention`); the
|
|
93
|
+
* `bb-logger` CDK construct is a no-op and no longer touches it. */
|
|
94
|
+
get handlerLogGroup(): cdk.aws_logs.ILogGroup;
|
|
85
95
|
private requireDefaultCompute;
|
|
86
96
|
/**
|
|
87
97
|
* The fullId used by child Scopes to compute their env var names,
|
|
@@ -103,6 +113,7 @@ export declare class BlocksBackend extends Construct {
|
|
|
103
113
|
* fallback to the (token-free) construct id should no resolvable name exist.
|
|
104
114
|
*/
|
|
105
115
|
get fullId(): string;
|
|
116
|
+
private _vpcOptions?;
|
|
106
117
|
private constructor();
|
|
107
118
|
static create(scope: Construct, id: string, props: CoreBlocksBackendProps): Promise<BlocksBackend>;
|
|
108
119
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blocks-backend.d.ts","sourceRoot":"","sources":["../../src/cdk/blocks-backend.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"blocks-backend.d.ts","sourceRoot":"","sources":["../../src/cdk/blocks-backend.ts"],"names":[],"mappings":"AAIA,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;AAEvC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,KAAK,EAAE,qBAAqB,EAAuB,MAAM,sCAAsC,CAAC;AAQvG;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAkB/C;AAED;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,QAAU,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IAClC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,QAAQ,EAAE,cAAc,CAAC;CACzB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,sBAAuB,SAAQ,kBAAkB;IACjE,8EAA8E;IAC9E,qBAAqB,EAAE,qBAAqB,CAAC;CAC7C;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,CAAC,EAAE,MAAM;;EAqGxF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,aAAc,SAAQ,SAAS;IAC3C,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;;wEAEoE;IACpE,IAAI,eAAe,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAE5C;IAED,OAAO,CAAC,qBAAqB;IAS7B;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,MAAM,IAAI,MAAM,CAUnB;IAED,OAAO,CAAC,WAAW,CAAC,CAAmB;IAEvC,OAAO;WA6BM,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,sBAAsB;CAyD/E"}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
3
4
|
import * as cdk from 'aws-cdk-lib';
|
|
4
5
|
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
5
6
|
import { CfnGroup } from 'aws-cdk-lib/aws-resourcegroups';
|
|
6
7
|
import { Construct } from 'constructs';
|
|
7
|
-
import { pathToFileURL } from 'node:url';
|
|
8
|
-
import { addBlocksStackMetadata } from './stack-metadata.js';
|
|
9
|
-
import { finalizeConfigRegistry, registerConfig } from './config-registry.js';
|
|
10
8
|
import { registerBuiltinRoutes } from '../builtin-routes.js';
|
|
9
|
+
import { getComputes } from './compute/compute-registry.js';
|
|
10
|
+
import { finalizeConfigRegistry, registerConfig } from './config-registry.js';
|
|
11
|
+
import { finalizeDashboards } from './dashboard-registry.js';
|
|
12
|
+
import { finalizeTracing } from './tracer-registry.js';
|
|
13
|
+
import { addBlocksStackMetadata } from './stack-metadata.js';
|
|
14
|
+
import { anyRequirementNeedsVpc, finalizeVpc, getOrCreateVpc, initializeVpc } from './vpc.js';
|
|
11
15
|
/**
|
|
12
16
|
* Validate that the Node.js process was started with `--conditions=cdk`.
|
|
13
17
|
*
|
|
@@ -19,7 +23,7 @@ import { registerBuiltinRoutes } from '../builtin-routes.js';
|
|
|
19
23
|
export function assertCdkConditionActive() {
|
|
20
24
|
const nodeOptions = process.env.NODE_OPTIONS ?? '';
|
|
21
25
|
const execArgv = process.execArgv ?? [];
|
|
22
|
-
const hasCdkCondition = execArgv.some(arg => arg === '--conditions=cdk') ||
|
|
26
|
+
const hasCdkCondition = execArgv.some((arg) => arg === '--conditions=cdk') ||
|
|
23
27
|
execArgv.some((arg, i) => (arg === '--conditions' || arg === '-C') && execArgv[i + 1] === 'cdk') ||
|
|
24
28
|
nodeOptions.includes('--conditions=cdk') ||
|
|
25
29
|
/(?:--conditions|-C)\s+cdk/.test(nodeOptions);
|
|
@@ -64,19 +68,24 @@ export function setupBlocksInfra(scope, props, id) {
|
|
|
64
68
|
// so permissions would quietly vanish. If a bring-your-own-role option is ever
|
|
65
69
|
// added, it must resolve to a mutable role (`{ mutable: true }`).
|
|
66
70
|
const executionRole = new iam.Role(scope, 'BlocksRole', {
|
|
67
|
-
// CompositePrincipal (rather than a bare ServicePrincipal) so
|
|
68
|
-
// compute
|
|
69
|
-
// (e.g.
|
|
71
|
+
// CompositePrincipal (rather than a bare ServicePrincipal) so a Building Block
|
|
72
|
+
// whose compute runs AS this shared role can add its own trust principal here
|
|
73
|
+
// (e.g. the Agent BB adds bedrock-agentcore in its CDK construct) — core stays
|
|
74
|
+
// agnostic and only Lambda is trusted by default.
|
|
70
75
|
assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('lambda.amazonaws.com')),
|
|
71
76
|
managedPolicies: [
|
|
72
77
|
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
|
|
78
|
+
// When Lambda is placed in a VPC it needs ENI management permissions
|
|
79
|
+
...(props.defaults.vpc
|
|
80
|
+
? [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaVPCAccessExecutionRole')]
|
|
81
|
+
: []),
|
|
73
82
|
],
|
|
74
83
|
});
|
|
75
84
|
// ── Resource Groups ───────────────────────────────────────────────────
|
|
76
85
|
let rootStack = cdk.Stack.of(scope);
|
|
77
86
|
while (rootStack.nestedStackParent)
|
|
78
87
|
rootStack = rootStack.nestedStackParent;
|
|
79
|
-
const groupPrefix =
|
|
88
|
+
const groupPrefix = id && id !== rootStack.stackName ? `${rootStack.stackName}-${id}` : rootStack.stackName;
|
|
80
89
|
new CfnGroup(scope, 'StackResources', {
|
|
81
90
|
name: `${groupPrefix}-resources`,
|
|
82
91
|
resourceQuery: {
|
|
@@ -109,12 +118,20 @@ export function setupBlocksInfra(scope, props, id) {
|
|
|
109
118
|
// ── Console redirect routes ───────────────────────────────────────────
|
|
110
119
|
const region = cdk.Fn.ref('AWS::Region');
|
|
111
120
|
const resourcesUrl = cdk.Fn.join('', [
|
|
112
|
-
'https://',
|
|
113
|
-
|
|
121
|
+
'https://',
|
|
122
|
+
region,
|
|
123
|
+
'.console.aws.amazon.com/resource-groups/group/',
|
|
124
|
+
`${groupPrefix}-resources`,
|
|
125
|
+
'?region=',
|
|
126
|
+
region,
|
|
114
127
|
]);
|
|
115
128
|
const settingsUrl = cdk.Fn.join('', [
|
|
116
|
-
'https://',
|
|
117
|
-
|
|
129
|
+
'https://',
|
|
130
|
+
region,
|
|
131
|
+
'.console.aws.amazon.com/resource-groups/group/',
|
|
132
|
+
`${groupPrefix}-settings`,
|
|
133
|
+
'?region=',
|
|
134
|
+
region,
|
|
118
135
|
]);
|
|
119
136
|
registerConfig(scope, 'BB_RESOURCES_GROUP_URL', resourcesUrl);
|
|
120
137
|
registerConfig(scope, 'BB_SETTINGS_GROUP_URL', settingsUrl);
|
|
@@ -140,6 +157,12 @@ export function setupBlocksInfra(scope, props, id) {
|
|
|
140
157
|
*/
|
|
141
158
|
export class BlocksBackend extends Construct {
|
|
142
159
|
backendHandlerPath;
|
|
160
|
+
/**
|
|
161
|
+
* Path to the app's backend module (`props.backendCDKPath`). Exposed so Building Blocks that
|
|
162
|
+
* co-bundle the backend at synth (e.g. the Agent BB's AgentCore Runtime) can discover it via
|
|
163
|
+
* `globalThis.CURRENT_BLOCKS_STACK.backendModulePath`.
|
|
164
|
+
*/
|
|
165
|
+
backendModulePath;
|
|
143
166
|
/** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
|
|
144
167
|
executionRole;
|
|
145
168
|
/** Infrastructure defaults for Building Blocks created under this backend. */
|
|
@@ -158,6 +181,12 @@ export class BlocksBackend extends Construct {
|
|
|
158
181
|
get apiUrl() {
|
|
159
182
|
return this.requireDefaultCompute().apiUrl;
|
|
160
183
|
}
|
|
184
|
+
/** The default compute's handler CloudWatch log group. Its retention comes from
|
|
185
|
+
* the compute's `logRetention` (falling back to `defaults.logRetention`); the
|
|
186
|
+
* `bb-logger` CDK construct is a no-op and no longer touches it. */
|
|
187
|
+
get handlerLogGroup() {
|
|
188
|
+
return this.requireDefaultCompute().logGroup;
|
|
189
|
+
}
|
|
161
190
|
requireDefaultCompute() {
|
|
162
191
|
if (!this._defaultCompute) {
|
|
163
192
|
throw new Error('Blocks backend not fully initialized — access .handler/.gateway/.apiUrl after BlocksBackend.create() resolves.');
|
|
@@ -194,15 +223,25 @@ export class BlocksBackend extends Construct {
|
|
|
194
223
|
}
|
|
195
224
|
return `${stackName}-${this.node.id}`;
|
|
196
225
|
}
|
|
226
|
+
_vpcOptions;
|
|
197
227
|
constructor(scope, id, props) {
|
|
198
228
|
super(scope, id);
|
|
199
229
|
this.backendHandlerPath = props.backendHandlerPath;
|
|
230
|
+
this.backendModulePath = props.backendCDKPath;
|
|
231
|
+
this._vpcOptions = props.defaults.vpc;
|
|
200
232
|
// Expose self to Building Blocks at CDK time
|
|
201
233
|
globalThis.CURRENT_BLOCKS_STACK = this;
|
|
202
234
|
// Store defaults on the backend (not the stack) so several BlocksBackends
|
|
203
235
|
// in one stack each keep their own posture; Building Blocks resolve them by
|
|
204
236
|
// walking up to their owning backend (see Scope.defaults).
|
|
205
237
|
this.defaults = props.defaults;
|
|
238
|
+
// Initialize VPC context before the default compute is created and before
|
|
239
|
+
// BBs are constructed, so both can discover it: the default compute
|
|
240
|
+
// (LambdaCompute) reads it via getVpcContext(this) to place its function in
|
|
241
|
+
// the VPC, and BBs (e.g. bb-data) read it to co-locate their resources.
|
|
242
|
+
if (this._vpcOptions) {
|
|
243
|
+
initializeVpc(this, this._vpcOptions);
|
|
244
|
+
}
|
|
206
245
|
const infra = setupBlocksInfra(this, props, id);
|
|
207
246
|
this.executionRole = infra.executionRole;
|
|
208
247
|
// The default compute (and thus handler/gateway) is created in create(),
|
|
@@ -233,7 +272,27 @@ export class BlocksBackend extends Construct {
|
|
|
233
272
|
}
|
|
234
273
|
addBlocksStackMetadata(cdk.Stack.of(backend));
|
|
235
274
|
// Finalize BB config → S3 (after all BBs have registered their config)
|
|
236
|
-
finalizeConfigRegistry(backend, backend.
|
|
275
|
+
finalizeConfigRegistry(backend, backend.executionRole, getComputes(backend));
|
|
276
|
+
// Tracing is presence-gated: if the app contains a Tracer, enable X-Ray on
|
|
277
|
+
// every compute. Runs before the dashboard finalize.
|
|
278
|
+
finalizeTracing(backend, backend.executionRole);
|
|
279
|
+
// Build any deferred Dashboards now that every compute's observability
|
|
280
|
+
// state is settled — so the dashboard is order-independent.
|
|
281
|
+
finalizeDashboards(backend);
|
|
282
|
+
// Finalize VPC. Derived resource: use the customer's if provided, else
|
|
283
|
+
// lazily create one only if a Building Block requires it.
|
|
284
|
+
if (backend._vpcOptions) {
|
|
285
|
+
finalizeVpc(backend, backend._vpcOptions);
|
|
286
|
+
}
|
|
287
|
+
else if (anyRequirementNeedsVpc(backend)) {
|
|
288
|
+
const derived = getOrCreateVpc(backend);
|
|
289
|
+
const options = { network: derived };
|
|
290
|
+
initializeVpc(backend, options);
|
|
291
|
+
finalizeVpc(backend, options);
|
|
292
|
+
cdk.Annotations.of(backend).addInfoV2('blocks:vpc:derived', 'A Building Block required a VPC and none was provided, so Blocks created one ' +
|
|
293
|
+
'(with a NAT gateway, which has an ongoing cost). Pass `defaults.vpc: { network }` to ' +
|
|
294
|
+
'bring your own. See packages/blocks/VPC.md.');
|
|
295
|
+
}
|
|
237
296
|
return backend;
|
|
238
297
|
}
|
|
239
298
|
}
|
|
@@ -14,6 +14,7 @@ import { BlocksBackend } from './blocks-backend.js';
|
|
|
14
14
|
import { BlocksPresets } from './blocks-defaults.js';
|
|
15
15
|
import { Compute } from './compute/compute.js';
|
|
16
16
|
import { Scope } from './index.js';
|
|
17
|
+
import { getVpcContext } from './vpc.js';
|
|
17
18
|
// A real app gets its default compute from @aws-blocks/bb-lambda-compute (via
|
|
18
19
|
// @aws-blocks/blocks), which core's own tests can't depend on. Use an
|
|
19
20
|
// equivalent inline stub: a Compute that owns a NodejsFunction + API Gateway,
|
|
@@ -25,15 +26,32 @@ class StubLambdaCompute extends Compute {
|
|
|
25
26
|
fn;
|
|
26
27
|
apiGateway;
|
|
27
28
|
apiUrl;
|
|
29
|
+
logGroup;
|
|
28
30
|
constructor(scope, id) {
|
|
29
31
|
super(id, { parent: scope });
|
|
32
|
+
this.logGroup = new cdk.aws_logs.LogGroup(this, 'HandlerLogGroup', {
|
|
33
|
+
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
34
|
+
});
|
|
35
|
+
// Discover the shared VPC context from the owning stack/backend, mirroring
|
|
36
|
+
// LambdaCompute — so the core-side VPC placement plumbing (initializeVpc →
|
|
37
|
+
// getVpcContext) is exercised by these tests without depending on
|
|
38
|
+
// @aws-blocks/bb-lambda-compute.
|
|
39
|
+
const vpcContext = getVpcContext(this);
|
|
30
40
|
this.fn = new lambda.NodejsFunction(this, 'Handler', {
|
|
31
41
|
entry: this.backendHandlerPath,
|
|
32
42
|
runtime: cdk.aws_lambda.Runtime.NODEJS_22_X,
|
|
33
43
|
handler: 'handler',
|
|
34
44
|
role: this.executionRole,
|
|
45
|
+
logGroup: this.logGroup,
|
|
35
46
|
environment: { BLOCKS_STACK_NAME: this.backendStackName },
|
|
36
47
|
bundling: { minify: true, esbuildArgs: { '--conditions': 'aws-runtime' } },
|
|
48
|
+
...(vpcContext
|
|
49
|
+
? {
|
|
50
|
+
vpc: vpcContext.vpc,
|
|
51
|
+
vpcSubnets: vpcContext.computeSubnets,
|
|
52
|
+
securityGroups: [vpcContext.computeSecurityGroup],
|
|
53
|
+
}
|
|
54
|
+
: {}),
|
|
37
55
|
});
|
|
38
56
|
this.apiGateway = new apigateway.RestApi(this, 'API', { restApiName: 'Blocks API' });
|
|
39
57
|
this.apiGateway.root.addProxy({
|
|
@@ -45,6 +63,16 @@ class StubLambdaCompute extends Compute {
|
|
|
45
63
|
setEnv(key, value) {
|
|
46
64
|
this.fn.addEnvironment(key, value);
|
|
47
65
|
}
|
|
66
|
+
applyTracing() { }
|
|
67
|
+
healthWidgets(_region) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
loggingWidgets(_region) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
tracingWidgets(_region) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
48
76
|
}
|
|
49
77
|
const stubComputeFactory = (root) => new StubLambdaCompute(root, 'DefaultCompute');
|
|
50
78
|
// Simulate the CDK condition being active (tests import CDK files directly)
|
|
@@ -60,7 +88,12 @@ const EXECUTION_ROLE_MARKER_ACTION = 'blocks-test:MarkerAction';
|
|
|
60
88
|
const importMetaHandlerPath = join(__dirname, '__fixtures__', 'import-meta-handler.js');
|
|
61
89
|
// Wraps BlocksBackend.create, injecting the stub default-compute factory the way
|
|
62
90
|
// @aws-blocks/blocks injects LambdaCompute — so tests don't repeat it 15 times.
|
|
63
|
-
const makeBackend = (scope, id, backendCDKPath) => BlocksBackend.create(scope, id, {
|
|
91
|
+
const makeBackend = (scope, id, backendCDKPath) => BlocksBackend.create(scope, id, {
|
|
92
|
+
backendHandlerPath: handlerPath,
|
|
93
|
+
backendCDKPath,
|
|
94
|
+
defaults: BlocksPresets.production,
|
|
95
|
+
defaultComputeFactory: stubComputeFactory,
|
|
96
|
+
});
|
|
64
97
|
describe('ESM cache-busting (multi-stage)', () => {
|
|
65
98
|
test('BlocksBackend.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
|
|
66
99
|
const app = new cdk.App();
|
|
@@ -123,11 +156,23 @@ describe('shared execution role', () => {
|
|
|
123
156
|
Principal: { Service: 'lambda.amazonaws.com' },
|
|
124
157
|
});
|
|
125
158
|
assert.ok(JSON.stringify(blocksRole.Properties.ManagedPolicyArns ?? []).includes('AWSLambdaBasicExecutionRole'), 'BlocksRole should attach AWSLambdaBasicExecutionRole');
|
|
159
|
+
// Core is BB-agnostic: with no Building Block that runs AS the shared role, the trust policy
|
|
160
|
+
// must NOT allow any compute principal beyond Lambda (e.g. bedrock-agentcore is added by the
|
|
161
|
+
// Agent BB's own construct, not here — see packages/bb-agent).
|
|
162
|
+
assert.ok(!JSON.stringify(blocksRole.Properties.AssumeRolePolicyDocument).includes('bedrock-agentcore'), 'core must not trust bedrock-agentcore by default (no agent → no AgentCore trust)');
|
|
126
163
|
// The Blocks handler references the shared role, not an auto-generated one.
|
|
127
164
|
template.hasResourceProperties('AWS::Lambda::Function', {
|
|
128
165
|
Role: { 'Fn::GetAtt': [blocksRoleId, 'Arn'] },
|
|
129
166
|
});
|
|
130
167
|
});
|
|
168
|
+
test('exposes backendModulePath (props.backendCDKPath) for co-bundling BBs', async () => {
|
|
169
|
+
const app = new cdk.App();
|
|
170
|
+
const parent = new cdk.Stack(app, 'BackendPathStack');
|
|
171
|
+
const backend = await makeBackend(parent, 'Blocks', sideEffectBackendPath);
|
|
172
|
+
// Building Blocks that co-bundle the app backend at synth time (e.g. the Agent BB's AgentCore
|
|
173
|
+
// Runtime) discover it via globalThis.CURRENT_BLOCKS_STACK.backendModulePath.
|
|
174
|
+
assert.strictEqual(backend.backendModulePath, sideEffectBackendPath);
|
|
175
|
+
});
|
|
131
176
|
test('a nested block resolves executionRole via the construct-tree walk', async () => {
|
|
132
177
|
const app = new cdk.App();
|
|
133
178
|
const parent = new cdk.Stack(app, 'RoleResolveStack');
|
|
@@ -277,3 +322,45 @@ describe('infrastructure defaults (backend-anchored)', () => {
|
|
|
277
322
|
assert.strictEqual(inner.defaults, BlocksPresets.sandbox);
|
|
278
323
|
});
|
|
279
324
|
});
|
|
325
|
+
describe('VPC placement', () => {
|
|
326
|
+
test('places the handler Lambda in the VPC with a security group when vpc is provided', async () => {
|
|
327
|
+
const app = new cdk.App();
|
|
328
|
+
const parent = new cdk.Stack(app, 'VpcParent', {
|
|
329
|
+
env: { account: '123456789012', region: 'us-east-1' },
|
|
330
|
+
});
|
|
331
|
+
const vpc = new cdk.aws_ec2.Vpc(parent, 'AppVpc', { maxAzs: 2, natGateways: 1 });
|
|
332
|
+
await BlocksBackend.create(parent, 'Blocks', {
|
|
333
|
+
backendHandlerPath: handlerPath,
|
|
334
|
+
backendCDKPath: sideEffectBackendPath,
|
|
335
|
+
defaults: { ...BlocksPresets.production, vpc: { network: vpc } },
|
|
336
|
+
defaultComputeFactory: stubComputeFactory,
|
|
337
|
+
});
|
|
338
|
+
const template = Template.fromStack(parent);
|
|
339
|
+
// The handler Lambda is VPC-attached: it has a VpcConfig with subnets + SGs.
|
|
340
|
+
template.hasResourceProperties('AWS::Lambda::Function', {
|
|
341
|
+
VpcConfig: Match.objectLike({
|
|
342
|
+
SubnetIds: Match.anyValue(),
|
|
343
|
+
SecurityGroupIds: Match.anyValue(),
|
|
344
|
+
}),
|
|
345
|
+
});
|
|
346
|
+
// The VPC-access managed policy is attached to the execution role.
|
|
347
|
+
const policies = template.findResources('AWS::IAM::Role');
|
|
348
|
+
const hasVpcManagedPolicy = Object.values(policies).some((role) => JSON.stringify(role.Properties?.ManagedPolicyArns ?? []).includes('AWSLambdaVPCAccessExecutionRole'));
|
|
349
|
+
assert.ok(hasVpcManagedPolicy, 'execution role should have the VPC access managed policy');
|
|
350
|
+
});
|
|
351
|
+
test('no VpcConfig on the handler when vpc is omitted', async () => {
|
|
352
|
+
const app = new cdk.App();
|
|
353
|
+
const parent = new cdk.Stack(app, 'NoVpcParent');
|
|
354
|
+
await BlocksBackend.create(parent, 'Blocks', {
|
|
355
|
+
backendHandlerPath: handlerPath,
|
|
356
|
+
backendCDKPath: sideEffectBackendPath,
|
|
357
|
+
defaults: BlocksPresets.production,
|
|
358
|
+
defaultComputeFactory: stubComputeFactory,
|
|
359
|
+
});
|
|
360
|
+
const template = Template.fromStack(parent);
|
|
361
|
+
const fns = template.findResources('AWS::Lambda::Function');
|
|
362
|
+
for (const fn of Object.values(fns)) {
|
|
363
|
+
assert.strictEqual(fn.Properties?.VpcConfig, undefined, 'handler must not have VpcConfig when no VPC is configured');
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
});
|