@aws-blocks/core 0.2.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 +43 -9
- package/dist/cdk/blocks-backend.d.ts.map +1 -1
- package/dist/cdk/blocks-backend.js +69 -73
- package/dist/cdk/blocks-backend.test.js +78 -69
- package/dist/cdk/blocks-defaults.d.ts +92 -2
- package/dist/cdk/blocks-defaults.d.ts.map +1 -1
- package/dist/cdk/blocks-defaults.js +22 -2
- package/dist/cdk/blocks-defaults.test.js +21 -2
- package/dist/cdk/blocks-stack.test.js +94 -37
- 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 +34 -0
- package/dist/cdk/compute/default-compute-factory.d.ts.map +1 -0
- package/dist/cdk/compute/default-compute-factory.js +3 -0
- 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 +76 -7
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +99 -9
- package/dist/cdk/internal.d.ts +1 -0
- package/dist/cdk/internal.d.ts.map +1 -1
- 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-secrets.d.ts +48 -0
- package/dist/hosting-secrets.d.ts.map +1 -0
- package/dist/hosting-secrets.js +57 -0
- package/dist/hosting-secrets.test.d.ts +2 -0
- package/dist/hosting-secrets.test.d.ts.map +1 -0
- package/dist/hosting-secrets.test.js +81 -0
- package/dist/hosting.d.ts +97 -6
- package/dist/hosting.d.ts.map +1 -1
- package/dist/hosting.js +91 -23
- package/dist/hosting.test.js +182 -10
- package/dist/index.cdk.d.ts +13 -12
- package/dist/index.cdk.d.ts.map +1 -1
- package/dist/index.cdk.js +13 -9
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -5
- 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 +196 -1
- package/dist/pipeline/index.d.ts +24 -2
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/index.js +41 -1
- package/dist/pipeline.test.d.ts +2 -0
- package/dist/pipeline.test.d.ts.map +1 -0
- package/dist/pipeline.test.js +47 -0
- package/dist/rpc.d.ts +21 -0
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +34 -0
- package/dist/rpc.test.js +38 -1
- package/dist/scripts/config.d.ts +15 -0
- package/dist/scripts/config.d.ts.map +1 -0
- package/dist/scripts/config.js +29 -0
- package/dist/scripts/deploy.d.ts.map +1 -1
- package/dist/scripts/deploy.js +4 -0
- package/dist/scripts/dev-server.d.ts +8 -0
- package/dist/scripts/dev-server.d.ts.map +1 -1
- package/dist/scripts/dev-server.js +31 -0
- package/dist/scripts/generate-client.d.ts.map +1 -1
- package/dist/scripts/generate-client.js +9 -4
- package/dist/scripts/generate-client.test.js +19 -4
- package/dist/scripts/index.d.ts +13 -10
- package/dist/scripts/index.d.ts.map +1 -1
- package/dist/scripts/index.js +11 -8
- package/dist/scripts/preflight-credentials.d.ts +30 -0
- package/dist/scripts/preflight-credentials.d.ts.map +1 -0
- package/dist/scripts/preflight-credentials.js +108 -0
- package/dist/scripts/preflight-credentials.test.d.ts +2 -0
- package/dist/scripts/preflight-credentials.test.d.ts.map +1 -0
- package/dist/scripts/preflight-credentials.test.js +77 -0
- package/dist/scripts/sandbox-args.test.d.ts +2 -0
- package/dist/scripts/sandbox-args.test.d.ts.map +1 -0
- package/dist/scripts/sandbox-args.test.js +77 -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 +106 -0
- package/dist/scripts/sandbox.d.ts.map +1 -1
- package/dist/scripts/sandbox.js +255 -40
- package/dist/scripts/secret.d.ts +15 -0
- package/dist/scripts/secret.d.ts.map +1 -0
- package/dist/scripts/secret.js +29 -0
- package/dist/scripts/secret.test.d.ts +2 -0
- package/dist/scripts/secret.test.d.ts.map +1 -0
- package/dist/scripts/secret.test.js +28 -0
- package/dist/scripts/typegen.d.ts +15 -0
- package/dist/scripts/typegen.d.ts.map +1 -0
- package/dist/scripts/typegen.js +16 -0
- package/dist/secret-naming.d.ts +58 -0
- package/dist/secret-naming.d.ts.map +1 -0
- package/dist/secret-naming.js +67 -0
- package/dist/secret-naming.test.d.ts +2 -0
- package/dist/secret-naming.test.d.ts.map +1 -0
- package/dist/secret-naming.test.js +40 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -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 +291 -272
- package/src/cdk/blocks-backend.ts +88 -83
- package/src/cdk/blocks-defaults.test.ts +25 -2
- package/src/cdk/blocks-defaults.ts +94 -2
- package/src/cdk/blocks-stack.test.ts +186 -118
- 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 +38 -0
- package/src/cdk/config-registry.test.ts +135 -0
- package/src/cdk/config-registry.ts +92 -34
- package/src/cdk/index.ts +129 -10
- package/src/cdk/internal.ts +1 -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 +21 -0
- package/src/hosting-secrets.test.ts +106 -0
- package/src/hosting-secrets.ts +106 -0
- package/src/hosting.test.ts +272 -63
- package/src/hosting.ts +200 -68
- package/src/index.cdk.ts +80 -38
- package/src/index.ts +35 -16
- package/src/lambda-handler.test.ts +215 -1
- package/src/lambda-handler.ts +54 -2
- package/src/pipeline/index.ts +64 -11
- package/src/pipeline.test.ts +55 -0
- package/src/rpc.test.ts +44 -1
- package/src/rpc.ts +41 -0
- package/src/scripts/config.ts +35 -0
- package/src/scripts/deploy.ts +5 -0
- package/src/scripts/dev-server.ts +40 -0
- package/src/scripts/generate-client.test.ts +43 -8
- package/src/scripts/generate-client.ts +70 -65
- package/src/scripts/index.ts +20 -17
- package/src/scripts/preflight-credentials.test.ts +110 -0
- package/src/scripts/preflight-credentials.ts +131 -0
- package/src/scripts/sandbox-args.test.ts +92 -0
- package/src/scripts/sandbox-empty-buckets.test.ts +191 -0
- package/src/scripts/sandbox.ts +287 -39
- package/src/scripts/secret.test.ts +35 -0
- package/src/scripts/secret.ts +35 -0
- package/src/scripts/typegen.ts +18 -0
- package/src/secret-naming.test.ts +59 -0
- package/src/secret-naming.ts +99 -0
- package/src/version.ts +1 -1
|
@@ -1,20 +1,69 @@
|
|
|
1
1
|
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
2
|
// SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
|
|
4
|
-
import { test, describe, before } from 'node:test';
|
|
5
4
|
import assert from 'node:assert';
|
|
6
|
-
import { fileURLToPath } from 'node:url';
|
|
7
5
|
import { dirname, join } from 'node:path';
|
|
6
|
+
import { before, describe, test } from 'node:test';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import * as cdk from 'aws-cdk-lib';
|
|
9
|
-
import {
|
|
9
|
+
import { Match, Template } from 'aws-cdk-lib/assertions';
|
|
10
|
+
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
|
|
10
11
|
import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
|
|
12
|
+
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
13
|
+
import type { Construct } from 'constructs';
|
|
14
|
+
import type { ScopeParent } from '../common/index.js';
|
|
15
|
+
import { BLOCKS_RPC_PREFIX } from '../constants.js';
|
|
11
16
|
import { BlocksBackend } from './blocks-backend.js';
|
|
12
17
|
import { BlocksPresets } from './blocks-defaults.js';
|
|
18
|
+
import { Compute } from './compute/compute.js';
|
|
19
|
+
import type { DefaultComputeFactory } from './compute/default-compute-factory.js';
|
|
13
20
|
import { Scope } from './index.js';
|
|
14
21
|
|
|
22
|
+
// A real app gets its default compute from @aws-blocks/bb-lambda-compute (via
|
|
23
|
+
// @aws-blocks/blocks), which core's own tests can't depend on. Use an
|
|
24
|
+
// equivalent inline stub: a Compute that owns a NodejsFunction + API Gateway,
|
|
25
|
+
// so create() can build the default and the handler/gateway/apiUrl accessors
|
|
26
|
+
// and synth-shape assertions have something real to resolve to. It is passed to
|
|
27
|
+
// each create() via the internal `defaultComputeFactory` option (see
|
|
28
|
+
// makeBackend), exactly as @aws-blocks/blocks injects LambdaCompute.
|
|
29
|
+
class StubLambdaCompute extends Compute {
|
|
30
|
+
readonly fn: lambda.NodejsFunction;
|
|
31
|
+
readonly apiGateway: apigateway.RestApi;
|
|
32
|
+
readonly apiUrl: string;
|
|
33
|
+
readonly logGroup: cdk.aws_logs.LogGroup;
|
|
34
|
+
|
|
35
|
+
constructor(scope: ScopeParent, id: string) {
|
|
36
|
+
super(id, { parent: scope });
|
|
37
|
+
this.logGroup = new cdk.aws_logs.LogGroup(this, 'HandlerLogGroup', {
|
|
38
|
+
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
39
|
+
});
|
|
40
|
+
this.fn = new lambda.NodejsFunction(this, 'Handler', {
|
|
41
|
+
entry: this.backendHandlerPath,
|
|
42
|
+
runtime: cdk.aws_lambda.Runtime.NODEJS_22_X,
|
|
43
|
+
handler: 'handler',
|
|
44
|
+
role: this.executionRole,
|
|
45
|
+
logGroup: this.logGroup,
|
|
46
|
+
environment: { BLOCKS_STACK_NAME: this.backendStackName },
|
|
47
|
+
bundling: { minify: true, esbuildArgs: { '--conditions': 'aws-runtime' } },
|
|
48
|
+
});
|
|
49
|
+
this.apiGateway = new apigateway.RestApi(this, 'API', { restApiName: 'Blocks API' });
|
|
50
|
+
this.apiGateway.root.addProxy({
|
|
51
|
+
defaultIntegration: new apigateway.LambdaIntegration(this.fn),
|
|
52
|
+
anyMethod: true,
|
|
53
|
+
});
|
|
54
|
+
this.apiUrl = `${this.apiGateway.url}${BLOCKS_RPC_PREFIX.slice(1)}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
setEnv(key: string, value: string): void {
|
|
58
|
+
this.fn.addEnvironment(key, value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const stubComputeFactory: DefaultComputeFactory = (root) => new StubLambdaCompute(root as never, 'DefaultCompute');
|
|
63
|
+
|
|
15
64
|
// Simulate the CDK condition being active (tests import CDK files directly)
|
|
16
65
|
before(() => {
|
|
17
|
-
|
|
66
|
+
process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS ?? '') + ' --conditions=cdk';
|
|
18
67
|
});
|
|
19
68
|
|
|
20
69
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -25,167 +74,159 @@ const fullIdConstructBackendPath = join(__dirname, '__fixtures__', 'fullid-const
|
|
|
25
74
|
const EXECUTION_ROLE_MARKER_ACTION = 'blocks-test:MarkerAction';
|
|
26
75
|
const importMetaHandlerPath = join(__dirname, '__fixtures__', 'import-meta-handler.js');
|
|
27
76
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const backend1 = await BlocksBackend.create(stack, 'Stage1', {
|
|
34
|
-
backendHandlerPath: handlerPath,
|
|
35
|
-
backendCDKPath: sideEffectBackendPath,
|
|
36
|
-
defaults: BlocksPresets.production,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
const backend2 = await BlocksBackend.create(stack, 'Stage2', {
|
|
40
|
-
backendHandlerPath: handlerPath,
|
|
41
|
-
backendCDKPath: sideEffectBackendPath,
|
|
42
|
-
defaults: BlocksPresets.production,
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const findMarker = (scope: cdk.aws_lambda_nodejs.NodejsFunction | any) =>
|
|
46
|
-
scope.node.tryFindChild('SideEffectMarker');
|
|
77
|
+
// Wraps BlocksBackend.create, injecting the stub default-compute factory the way
|
|
78
|
+
// @aws-blocks/blocks injects LambdaCompute — so tests don't repeat it 15 times.
|
|
79
|
+
const makeBackend = (scope: Construct, id: string, backendCDKPath: string) =>
|
|
80
|
+
BlocksBackend.create(scope, id, { backendHandlerPath: handlerPath, backendCDKPath, defaults: BlocksPresets.production, defaultComputeFactory: stubComputeFactory });
|
|
47
81
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
82
|
+
describe('ESM cache-busting (multi-stage)', () => {
|
|
83
|
+
test('BlocksBackend.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
|
|
84
|
+
const app = new cdk.App();
|
|
85
|
+
const stack = new cdk.Stack(app, 'TestStack');
|
|
86
|
+
|
|
87
|
+
const backend1 = await makeBackend(stack, 'Stage1', sideEffectBackendPath);
|
|
88
|
+
|
|
89
|
+
const backend2 = await makeBackend(stack, 'Stage2', sideEffectBackendPath);
|
|
90
|
+
|
|
91
|
+
const findMarker = (scope: cdk.aws_lambda_nodejs.NodejsFunction | any) =>
|
|
92
|
+
scope.node.tryFindChild('SideEffectMarker');
|
|
93
|
+
|
|
94
|
+
assert.ok(
|
|
95
|
+
findMarker(backend1),
|
|
96
|
+
'Stage1 backend should have SideEffectMarker construct from module side effect',
|
|
97
|
+
);
|
|
98
|
+
assert.ok(
|
|
99
|
+
findMarker(backend2),
|
|
100
|
+
'Stage2 backend should have SideEffectMarker construct from re-executed module',
|
|
101
|
+
);
|
|
102
|
+
});
|
|
57
103
|
});
|
|
58
104
|
|
|
59
105
|
describe('synth shape (drop into existing stack)', () => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
backendCDKPath: sideEffectBackendPath,
|
|
89
|
-
defaults: BlocksPresets.production,
|
|
90
|
-
});
|
|
91
|
-
await BlocksBackend.create(parent, 'BackendB', {
|
|
92
|
-
backendHandlerPath: handlerPath,
|
|
93
|
-
backendCDKPath: sideEffectBackendPath,
|
|
94
|
-
defaults: BlocksPresets.production,
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const template = Template.fromStack(parent);
|
|
98
|
-
template.resourceCountIs('AWS::ApiGateway::RestApi', 2);
|
|
99
|
-
});
|
|
106
|
+
test('BlocksBackend lives inside the parent stack and synthesizes Lambda + API Gateway', async () => {
|
|
107
|
+
const app = new cdk.App();
|
|
108
|
+
const parent = new cdk.Stack(app, 'MyExistingStack');
|
|
109
|
+
|
|
110
|
+
const backend = await makeBackend(parent, 'Blocks', sideEffectBackendPath);
|
|
111
|
+
|
|
112
|
+
// Public surface mirrors BlocksStack.
|
|
113
|
+
assert.ok(backend.handler, 'BlocksBackend should expose .handler');
|
|
114
|
+
assert.ok(backend.gateway, 'BlocksBackend should expose .gateway');
|
|
115
|
+
assert.ok(backend.apiUrl, 'BlocksBackend should expose .apiUrl');
|
|
116
|
+
|
|
117
|
+
// Synth produces the expected resources inside the parent stack —
|
|
118
|
+
// no separate stack is created.
|
|
119
|
+
const template = Template.fromStack(parent);
|
|
120
|
+
template.hasResourceProperties('AWS::Lambda::Function', {});
|
|
121
|
+
template.resourceCountIs('AWS::ApiGateway::RestApi', 1);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test('multiple BlocksBackends in the same parent stack do not collide', async () => {
|
|
125
|
+
const app = new cdk.App();
|
|
126
|
+
const parent = new cdk.Stack(app, 'MultiBackendStack');
|
|
127
|
+
|
|
128
|
+
await makeBackend(parent, 'BackendA', sideEffectBackendPath);
|
|
129
|
+
await makeBackend(parent, 'BackendB', sideEffectBackendPath);
|
|
130
|
+
|
|
131
|
+
const template = Template.fromStack(parent);
|
|
132
|
+
template.resourceCountIs('AWS::ApiGateway::RestApi', 2);
|
|
133
|
+
});
|
|
100
134
|
});
|
|
101
135
|
|
|
102
136
|
describe('shared execution role', () => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
137
|
+
test('exposes executionRole on the backend', async () => {
|
|
138
|
+
const app = new cdk.App();
|
|
139
|
+
const parent = new cdk.Stack(app, 'RoleSurfaceStack');
|
|
140
|
+
|
|
141
|
+
const backend = await makeBackend(parent, 'Blocks', sideEffectBackendPath);
|
|
142
|
+
|
|
143
|
+
assert.ok(backend.executionRole, 'BlocksBackend should expose .executionRole');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('synth produces a Lambda-assumable role with basic execution, and the handler uses it', async () => {
|
|
147
|
+
const app = new cdk.App();
|
|
148
|
+
const parent = new cdk.Stack(app, 'RoleSynthStack');
|
|
149
|
+
|
|
150
|
+
await makeBackend(parent, 'Blocks', sideEffectBackendPath);
|
|
151
|
+
|
|
152
|
+
const template = Template.fromStack(parent);
|
|
153
|
+
|
|
154
|
+
// The shared role (logical id derived from the 'BlocksRole' construct id)
|
|
155
|
+
// is assumable by Lambda and carries AWSLambdaBasicExecutionRole (so
|
|
156
|
+
// CloudWatch Logs keep working after swapping off the auto-role). Other
|
|
157
|
+
// roles exist (API Gateway CloudWatch role, config BucketDeployment role),
|
|
158
|
+
// so we target ours by logical id.
|
|
159
|
+
const roles = template.findResources('AWS::IAM::Role');
|
|
160
|
+
const blocksRoleId = Object.keys(roles).find((k) => k.includes('BlocksRole'));
|
|
161
|
+
assert.ok(blocksRoleId, 'expected a role from the BlocksRole construct');
|
|
162
|
+
const blocksRole = roles[blocksRoleId];
|
|
163
|
+
assert.deepStrictEqual(blocksRole.Properties.AssumeRolePolicyDocument.Statement[0], {
|
|
164
|
+
Action: 'sts:AssumeRole',
|
|
165
|
+
Effect: 'Allow',
|
|
166
|
+
Principal: { Service: 'lambda.amazonaws.com' },
|
|
167
|
+
});
|
|
168
|
+
assert.ok(
|
|
169
|
+
JSON.stringify(blocksRole.Properties.ManagedPolicyArns ?? []).includes('AWSLambdaBasicExecutionRole'),
|
|
170
|
+
'BlocksRole should attach AWSLambdaBasicExecutionRole',
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Core is BB-agnostic: with no Building Block that runs AS the shared role, the trust policy
|
|
174
|
+
// must NOT allow any compute principal beyond Lambda (e.g. bedrock-agentcore is added by the
|
|
175
|
+
// Agent BB's own construct, not here — see packages/bb-agent).
|
|
176
|
+
assert.ok(
|
|
177
|
+
!JSON.stringify(blocksRole.Properties.AssumeRolePolicyDocument).includes('bedrock-agentcore'),
|
|
178
|
+
'core must not trust bedrock-agentcore by default (no agent → no AgentCore trust)',
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
// The Blocks handler references the shared role, not an auto-generated one.
|
|
182
|
+
template.hasResourceProperties('AWS::Lambda::Function', {
|
|
183
|
+
Role: { 'Fn::GetAtt': [blocksRoleId, 'Arn'] },
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test('exposes backendModulePath (props.backendCDKPath) for co-bundling BBs', async () => {
|
|
188
|
+
const app = new cdk.App();
|
|
189
|
+
const parent = new cdk.Stack(app, 'BackendPathStack');
|
|
190
|
+
|
|
191
|
+
const backend = await makeBackend(parent, 'Blocks', sideEffectBackendPath);
|
|
192
|
+
|
|
193
|
+
// Building Blocks that co-bundle the app backend at synth time (e.g. the Agent BB's AgentCore
|
|
194
|
+
// Runtime) discover it via globalThis.CURRENT_BLOCKS_STACK.backendModulePath.
|
|
195
|
+
assert.strictEqual(backend.backendModulePath, sideEffectBackendPath);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('a nested block resolves executionRole via the construct-tree walk', async () => {
|
|
199
|
+
const app = new cdk.App();
|
|
200
|
+
const parent = new cdk.Stack(app, 'RoleResolveStack');
|
|
201
|
+
|
|
202
|
+
const backend = await makeBackend(parent, 'Blocks', sideEffectBackendPath);
|
|
203
|
+
|
|
204
|
+
// Build nested Scopes under the backend (outer → inner), the same shape a
|
|
205
|
+
// real Building Block tree has, and grant a uniquely-named marker action to
|
|
206
|
+
// `this.executionRole` from the innermost scope. If the getter's tree-walk
|
|
207
|
+
// failed, it would resolve the wrong role (or throw), and the marker would
|
|
208
|
+
// not land on the backend's shared role.
|
|
209
|
+
// `create()` sets globalThis.CURRENT_BLOCKS_STACK = backend, so a parent-less
|
|
210
|
+
// Scope attaches under the backend (the same way a real backend module's
|
|
211
|
+
// top-level blocks do); `inner` is then nested one level deeper.
|
|
212
|
+
const outer = new Scope('outer');
|
|
213
|
+
const inner = new Scope('inner', { parent: outer });
|
|
214
|
+
|
|
215
|
+
// Resolves to the backend's shared role from two levels deep.
|
|
216
|
+
assert.strictEqual(inner.executionRole, backend.executionRole);
|
|
217
|
+
|
|
218
|
+
inner.executionRole.addToPrincipalPolicy(
|
|
219
|
+
new PolicyStatement({ actions: [EXECUTION_ROLE_MARKER_ACTION], resources: ['*'] }),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// The grant lands on the shared role's default inline policy (AWS::IAM::Policy).
|
|
223
|
+
const template = Template.fromStack(parent);
|
|
224
|
+
template.hasResourceProperties('AWS::IAM::Policy', {
|
|
225
|
+
PolicyDocument: {
|
|
226
|
+
Statement: Match.arrayWith([Match.objectLike({ Action: EXECUTION_ROLE_MARKER_ACTION })]),
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
});
|
|
189
230
|
});
|
|
190
231
|
|
|
191
232
|
describe('CJS bundle: import.meta.url in the handler is shimmed (no Lambda-load crash)', () => {
|
|
@@ -204,131 +245,106 @@ describe('CJS bundle: import.meta.url in the handler is shimmed (no Lambda-load
|
|
|
204
245
|
backendHandlerPath: importMetaHandlerPath,
|
|
205
246
|
backendCDKPath: sideEffectBackendPath,
|
|
206
247
|
defaults: BlocksPresets.production,
|
|
248
|
+
defaultComputeFactory: stubComputeFactory,
|
|
207
249
|
}),
|
|
208
250
|
);
|
|
209
251
|
});
|
|
210
252
|
});
|
|
211
253
|
|
|
212
254
|
describe('factory function support', () => {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
255
|
+
test('BlocksBackend.create() calls default export function with the backend instance', async () => {
|
|
256
|
+
const app = new cdk.App();
|
|
257
|
+
const stack = new cdk.Stack(app, 'FactoryTestStack');
|
|
216
258
|
|
|
217
|
-
|
|
218
|
-
backendHandlerPath: handlerPath,
|
|
219
|
-
backendCDKPath: factoryBackendPath,
|
|
220
|
-
defaults: BlocksPresets.production,
|
|
221
|
-
});
|
|
259
|
+
const backend = await makeBackend(stack, 'FactoryStage', factoryBackendPath);
|
|
222
260
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
261
|
+
const marker = backend.node.tryFindChild('FactoryMarker');
|
|
262
|
+
assert.ok(marker, 'Factory function should have created FactoryMarker on the backend');
|
|
263
|
+
});
|
|
226
264
|
});
|
|
227
265
|
|
|
228
266
|
describe('fullId is token-free (construct IDs / env-var keys)', () => {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
defaults: BlocksPresets.production,
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
const template = Template.fromStack(nested);
|
|
314
|
-
const fns = template.findResources('AWS::Lambda::Function');
|
|
315
|
-
const envValues = Object.values(fns)
|
|
316
|
-
.map((fn: any) => fn.Properties?.Environment?.Variables?.BLOCKS_STACK_NAME)
|
|
317
|
-
.filter((v): v is string => typeof v === 'string');
|
|
318
|
-
|
|
319
|
-
assert.ok(envValues.length > 0, 'expected a Lambda with a BLOCKS_STACK_NAME env var');
|
|
320
|
-
for (const value of envValues) {
|
|
321
|
-
assert.strictEqual(
|
|
322
|
-
value,
|
|
323
|
-
backend.fullId,
|
|
324
|
-
'BLOCKS_STACK_NAME must equal BlocksBackend.fullId so runtime names match synth',
|
|
325
|
-
);
|
|
326
|
-
assert.ok(
|
|
327
|
-
!cdk.Token.isUnresolved(value),
|
|
328
|
-
`BLOCKS_STACK_NAME must be token-free, got: ${value}`,
|
|
329
|
-
);
|
|
330
|
-
}
|
|
331
|
-
});
|
|
267
|
+
test('top-level stack: fullId is {stackName}-{id} and resolvable', async () => {
|
|
268
|
+
const app = new cdk.App();
|
|
269
|
+
const stack = new cdk.Stack(app, 'TopLevelStack');
|
|
270
|
+
|
|
271
|
+
const backend = await makeBackend(stack, 'blocks', sideEffectBackendPath);
|
|
272
|
+
|
|
273
|
+
assert.strictEqual(backend.fullId, 'TopLevelStack-blocks');
|
|
274
|
+
assert.ok(!cdk.Token.isUnresolved(backend.fullId), 'fullId must not contain a token');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('nested stack: fullId stays token-free (regression for #714 / Amplify Gen2)', async () => {
|
|
278
|
+
// Amplify Gen2 wires Blocks into a NestedStack via backend.createStack('blocks').
|
|
279
|
+
// A NestedStack has a tokenized stackName that only resolves at deploy time —
|
|
280
|
+
// embedding it in fullId broke construct IDs with
|
|
281
|
+
// "ID components may not include unresolved tokens".
|
|
282
|
+
const app = new cdk.App();
|
|
283
|
+
const parent = new cdk.Stack(app, 'ParentStack');
|
|
284
|
+
const nested = new cdk.NestedStack(parent, 'blocks');
|
|
285
|
+
|
|
286
|
+
// Sanity: the nested stack's own name really is a token.
|
|
287
|
+
assert.ok(
|
|
288
|
+
cdk.Token.isUnresolved(nested.stackName),
|
|
289
|
+
'precondition: NestedStack.stackName should be an unresolved token',
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const backend = await makeBackend(nested, 'blocks', sideEffectBackendPath);
|
|
293
|
+
|
|
294
|
+
assert.ok(
|
|
295
|
+
!cdk.Token.isUnresolved(backend.fullId),
|
|
296
|
+
`fullId must be token-free inside a nested stack, got: ${backend.fullId}`,
|
|
297
|
+
);
|
|
298
|
+
// Falls back to the top-level (concrete) stack name, keeping uniqueness.
|
|
299
|
+
assert.strictEqual(backend.fullId, 'ParentStack-blocks');
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
test('child Scope can use fullId as a construct ID inside a nested stack', async () => {
|
|
303
|
+
// The fixture mirrors bb-distributed-data: it builds a construct whose ID is
|
|
304
|
+
// `${this.fullId}Marker`. If fullId carried a token, synth would throw.
|
|
305
|
+
const app = new cdk.App();
|
|
306
|
+
const parent = new cdk.Stack(app, 'Gen2ParentStack');
|
|
307
|
+
const nested = new cdk.NestedStack(parent, 'blocks');
|
|
308
|
+
|
|
309
|
+
const backend = await makeBackend(nested, 'blocks', fullIdConstructBackendPath);
|
|
310
|
+
|
|
311
|
+
// The construct ID is `${scope.fullId}Marker` → `ParentStack-blocks-blocks-dbMarker`.
|
|
312
|
+
const expectedId = `${backend.fullId}-dbMarker`;
|
|
313
|
+
assert.ok(nested.node.tryFindChild(expectedId), `expected a child construct with id "${expectedId}"`);
|
|
314
|
+
|
|
315
|
+
// Full synth must not throw on unresolved-token-in-construct-id.
|
|
316
|
+
assert.doesNotThrow(() => app.synth());
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test('BLOCKS_STACK_NAME env var equals fullId (CDK-time ↔ runtime invariant)', async () => {
|
|
320
|
+
// Physical resource names (DynamoDB tables, DSQL env-var keys, IAM ARNs) are
|
|
321
|
+
// derived from fullId on BOTH sides: at synth via BlocksBackend.fullId, and at
|
|
322
|
+
// runtime via the root parent `{ id: process.env.BLOCKS_STACK_NAME }`. They MUST
|
|
323
|
+
// be byte-for-byte identical, otherwise the runtime looks up names/grants that
|
|
324
|
+
// were never created. BlocksBackend keeps them in sync by writing fullId into the
|
|
325
|
+
// handler's BLOCKS_STACK_NAME env var — assert that contract holds and is token-free.
|
|
326
|
+
const app = new cdk.App();
|
|
327
|
+
const parent = new cdk.Stack(app, 'InvariantParent');
|
|
328
|
+
const nested = new cdk.NestedStack(parent, 'blocks');
|
|
329
|
+
|
|
330
|
+
const backend = await makeBackend(nested, 'blocks', sideEffectBackendPath);
|
|
331
|
+
|
|
332
|
+
const template = Template.fromStack(nested);
|
|
333
|
+
const fns = template.findResources('AWS::Lambda::Function');
|
|
334
|
+
const envValues = Object.values(fns)
|
|
335
|
+
.map((fn: any) => fn.Properties?.Environment?.Variables?.BLOCKS_STACK_NAME)
|
|
336
|
+
.filter((v): v is string => typeof v === 'string');
|
|
337
|
+
|
|
338
|
+
assert.ok(envValues.length > 0, 'expected a Lambda with a BLOCKS_STACK_NAME env var');
|
|
339
|
+
for (const value of envValues) {
|
|
340
|
+
assert.strictEqual(
|
|
341
|
+
value,
|
|
342
|
+
backend.fullId,
|
|
343
|
+
'BLOCKS_STACK_NAME must equal BlocksBackend.fullId so runtime names match synth',
|
|
344
|
+
);
|
|
345
|
+
assert.ok(!cdk.Token.isUnresolved(value), `BLOCKS_STACK_NAME must be token-free, got: ${value}`);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
332
348
|
});
|
|
333
349
|
|
|
334
350
|
describe('infrastructure defaults (backend-anchored)', () => {
|
|
@@ -340,11 +356,13 @@ describe('infrastructure defaults (backend-anchored)', () => {
|
|
|
340
356
|
backendHandlerPath: handlerPath,
|
|
341
357
|
backendCDKPath: sideEffectBackendPath,
|
|
342
358
|
defaults: BlocksPresets.production,
|
|
359
|
+
defaultComputeFactory: stubComputeFactory,
|
|
343
360
|
});
|
|
344
361
|
const b = await BlocksBackend.create(stack, 'B', {
|
|
345
362
|
backendHandlerPath: handlerPath,
|
|
346
363
|
backendCDKPath: sideEffectBackendPath,
|
|
347
364
|
defaults: BlocksPresets.sandbox,
|
|
365
|
+
defaultComputeFactory: stubComputeFactory,
|
|
348
366
|
});
|
|
349
367
|
|
|
350
368
|
// Two backends in one stack must NOT clobber each other — defaults are
|
|
@@ -361,6 +379,7 @@ describe('infrastructure defaults (backend-anchored)', () => {
|
|
|
361
379
|
backendHandlerPath: handlerPath,
|
|
362
380
|
backendCDKPath: sideEffectBackendPath,
|
|
363
381
|
defaults: BlocksPresets.sandbox,
|
|
382
|
+
defaultComputeFactory: stubComputeFactory,
|
|
364
383
|
});
|
|
365
384
|
|
|
366
385
|
// A Scope under the backend resolves scope.defaults by walking up to it.
|