@aws-blocks/core 0.1.17 → 0.1.18
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/dist/cdk/blocks-backend.d.ts +4 -0
- package/dist/cdk/blocks-backend.d.ts.map +1 -1
- package/dist/cdk/blocks-backend.js +23 -1
- package/dist/cdk/blocks-backend.test.js +71 -1
- package/dist/cdk/blocks-stack.test.js +32 -1
- package/dist/cdk/index.d.ts +13 -0
- package/dist/cdk/index.d.ts.map +1 -1
- package/dist/cdk/index.js +24 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/cdk/blocks-backend.test.ts +90 -1
- package/src/cdk/blocks-backend.ts +24 -1
- package/src/cdk/blocks-stack.test.ts +41 -1
- package/src/cdk/index.ts +25 -0
- package/src/version.ts +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as cdk from 'aws-cdk-lib';
|
|
2
2
|
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
|
|
3
|
+
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
3
4
|
import { Construct } from 'constructs';
|
|
4
5
|
/**
|
|
5
6
|
* Validate that the Node.js process was started with `--conditions=cdk`.
|
|
@@ -19,6 +20,7 @@ export declare function setupBlocksInfra(scope: Construct, props: BlocksBackendP
|
|
|
19
20
|
handler: cdk.aws_lambda_nodejs.NodejsFunction;
|
|
20
21
|
gateway: cdk.aws_apigateway.RestApi;
|
|
21
22
|
apiUrl: string;
|
|
23
|
+
executionRole: cdk.aws_iam.Role;
|
|
22
24
|
};
|
|
23
25
|
/**
|
|
24
26
|
* Standalone CDK construct that provisions the Blocks backend: a single Lambda
|
|
@@ -42,6 +44,8 @@ export declare class BlocksBackend extends Construct {
|
|
|
42
44
|
readonly gateway: apigateway.RestApi;
|
|
43
45
|
readonly handler: cdk.aws_lambda_nodejs.NodejsFunction;
|
|
44
46
|
readonly backendHandlerPath: string;
|
|
47
|
+
/** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
|
|
48
|
+
readonly executionRole: iam.IRole;
|
|
45
49
|
/**
|
|
46
50
|
* The fullId used by child Scopes to compute their env var names,
|
|
47
51
|
* construct IDs, and physical resource names (e.g., DynamoDB table 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;AAEnC,OAAO,KAAK,UAAU,MAAM,4BAA4B,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;AAEnC,OAAO,KAAK,UAAU,MAAM,4BAA4B,CAAC;AACzD,OAAO,KAAK,GAAG,MAAM,qBAAqB,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAQvC;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAkB/C;AAED,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,kBAAkB,EAAE,EAAE,CAAC,EAAE,MAAM;;;;;EA6HxF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,aAAc,SAAQ,SAAS;IAC1C,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,OAAO,EAAE,UAAU,CAAC,OAAO,CAAC;IAC5C,SAAgB,OAAO,EAAE,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAAC;IAC9D,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAC3C,yFAAyF;IACzF,SAAgB,aAAa,EAAE,GAAG,CAAC,KAAK,CAAC;IAEzC;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,MAAM,IAAI,MAAM,CAUnB;IAED,OAAO;WAoBM,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB;CAsB5E"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as cdk from 'aws-cdk-lib';
|
|
4
4
|
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
5
5
|
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
|
|
6
|
+
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
6
7
|
import { CfnGroup } from 'aws-cdk-lib/aws-resourcegroups';
|
|
7
8
|
import { Construct } from 'constructs';
|
|
8
9
|
import { pathToFileURL } from 'node:url';
|
|
@@ -35,10 +36,28 @@ export function assertCdkConditionActive() {
|
|
|
35
36
|
}
|
|
36
37
|
/** Shared infra setup — creates Lambda + API Gateway on the given scope. */
|
|
37
38
|
export function setupBlocksInfra(scope, props, id) {
|
|
39
|
+
// ── Shared execution role ──────────────────────────────────────────────
|
|
40
|
+
// A single IAM role that every Building Block grants to. Provisioned here so
|
|
41
|
+
// it exists before the backend module is imported (Building Blocks reach it
|
|
42
|
+
// via `scope.executionRole`). Block grants sit on the role's default (inline)
|
|
43
|
+
// policy, exactly as they did on the auto-generated NodejsFunction role.
|
|
44
|
+
//
|
|
45
|
+
// AWSLambdaBasicExecutionRole is attached explicitly because the auto-role
|
|
46
|
+
// included it by default — omitting it would silently break CloudWatch Logs.
|
|
47
|
+
const executionRole = new iam.Role(scope, 'BlocksRole', {
|
|
48
|
+
// CompositePrincipal (rather than a bare ServicePrincipal) so additional
|
|
49
|
+
// compute types can assume this same shared role as they are introduced
|
|
50
|
+
// (e.g. ECS tasks via ecs-tasks.amazonaws.com), by adding principals here.
|
|
51
|
+
assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('lambda.amazonaws.com')),
|
|
52
|
+
managedPolicies: [
|
|
53
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
|
|
54
|
+
],
|
|
55
|
+
});
|
|
38
56
|
const handler = new lambda.NodejsFunction(scope, 'Handler', {
|
|
39
57
|
entry: props.backendHandlerPath,
|
|
40
58
|
runtime: DEFAULT_NODE_RUNTIME,
|
|
41
59
|
handler: 'handler',
|
|
60
|
+
role: executionRole,
|
|
42
61
|
memorySize: 2048,
|
|
43
62
|
timeout: cdk.Duration.seconds(60 * 15),
|
|
44
63
|
environment: {
|
|
@@ -126,7 +145,7 @@ export function setupBlocksInfra(scope, props, id) {
|
|
|
126
145
|
registerConfig(scope, 'BB_RESOURCES_GROUP_URL', resourcesUrl);
|
|
127
146
|
registerConfig(scope, 'BB_SETTINGS_GROUP_URL', settingsUrl);
|
|
128
147
|
registerBuiltinRoutes();
|
|
129
|
-
return { handler, gateway: api, apiUrl: `${api.url}${BLOCKS_RPC_PREFIX.slice(1)}
|
|
148
|
+
return { handler, gateway: api, apiUrl: `${api.url}${BLOCKS_RPC_PREFIX.slice(1)}`, executionRole };
|
|
130
149
|
}
|
|
131
150
|
/**
|
|
132
151
|
* Standalone CDK construct that provisions the Blocks backend: a single Lambda
|
|
@@ -150,6 +169,8 @@ export class BlocksBackend extends Construct {
|
|
|
150
169
|
gateway;
|
|
151
170
|
handler;
|
|
152
171
|
backendHandlerPath;
|
|
172
|
+
/** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
|
|
173
|
+
executionRole;
|
|
153
174
|
/**
|
|
154
175
|
* The fullId used by child Scopes to compute their env var names,
|
|
155
176
|
* construct IDs, and physical resource names (e.g., DynamoDB table names).
|
|
@@ -189,6 +210,7 @@ export class BlocksBackend extends Construct {
|
|
|
189
210
|
this.handler = infra.handler;
|
|
190
211
|
this.gateway = infra.gateway;
|
|
191
212
|
this.apiUrl = infra.apiUrl;
|
|
213
|
+
this.executionRole = infra.executionRole;
|
|
192
214
|
// Override BLOCKS_STACK_NAME to include the parent stack name so runtime
|
|
193
215
|
// resource lookups (DynamoDB table names) match the CDK-time fullId
|
|
194
216
|
// and are unique per deployment.
|
|
@@ -5,8 +5,10 @@ import assert from 'node:assert';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { dirname, join } from 'node:path';
|
|
7
7
|
import * as cdk from 'aws-cdk-lib';
|
|
8
|
-
import { Template } from 'aws-cdk-lib/assertions';
|
|
8
|
+
import { Template, Match } from 'aws-cdk-lib/assertions';
|
|
9
|
+
import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
|
|
9
10
|
import { BlocksBackend } from './blocks-backend.js';
|
|
11
|
+
import { Scope } from './index.js';
|
|
10
12
|
// Simulate the CDK condition being active (tests import CDK files directly)
|
|
11
13
|
before(() => {
|
|
12
14
|
process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS ?? '') + ' --conditions=cdk';
|
|
@@ -16,6 +18,7 @@ const handlerPath = join(__dirname, '__fixtures__', 'handler.js');
|
|
|
16
18
|
const sideEffectBackendPath = join(__dirname, '__fixtures__', 'side-effect-backend.js');
|
|
17
19
|
const factoryBackendPath = join(__dirname, '__fixtures__', 'factory-backend.js');
|
|
18
20
|
const fullIdConstructBackendPath = join(__dirname, '__fixtures__', 'fullid-construct-backend.js');
|
|
21
|
+
const EXECUTION_ROLE_MARKER_ACTION = 'blocks-test:MarkerAction';
|
|
19
22
|
describe('ESM cache-busting (multi-stage)', () => {
|
|
20
23
|
test('BlocksBackend.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
|
|
21
24
|
const app = new cdk.App();
|
|
@@ -66,6 +69,73 @@ describe('synth shape (drop into existing stack)', () => {
|
|
|
66
69
|
template.resourceCountIs('AWS::ApiGateway::RestApi', 2);
|
|
67
70
|
});
|
|
68
71
|
});
|
|
72
|
+
describe('shared execution role', () => {
|
|
73
|
+
test('exposes executionRole on the backend', async () => {
|
|
74
|
+
const app = new cdk.App();
|
|
75
|
+
const parent = new cdk.Stack(app, 'RoleSurfaceStack');
|
|
76
|
+
const backend = await BlocksBackend.create(parent, 'Blocks', {
|
|
77
|
+
backendHandlerPath: handlerPath,
|
|
78
|
+
backendCDKPath: sideEffectBackendPath,
|
|
79
|
+
});
|
|
80
|
+
assert.ok(backend.executionRole, 'BlocksBackend should expose .executionRole');
|
|
81
|
+
});
|
|
82
|
+
test('synth produces a Lambda-assumable role with basic execution, and the handler uses it', async () => {
|
|
83
|
+
const app = new cdk.App();
|
|
84
|
+
const parent = new cdk.Stack(app, 'RoleSynthStack');
|
|
85
|
+
await BlocksBackend.create(parent, 'Blocks', {
|
|
86
|
+
backendHandlerPath: handlerPath,
|
|
87
|
+
backendCDKPath: sideEffectBackendPath,
|
|
88
|
+
});
|
|
89
|
+
const template = Template.fromStack(parent);
|
|
90
|
+
// The shared role (logical id derived from the 'BlocksRole' construct id)
|
|
91
|
+
// is assumable by Lambda and carries AWSLambdaBasicExecutionRole (so
|
|
92
|
+
// CloudWatch Logs keep working after swapping off the auto-role). Other
|
|
93
|
+
// roles exist (API Gateway CloudWatch role, config BucketDeployment role),
|
|
94
|
+
// so we target ours by logical id.
|
|
95
|
+
const roles = template.findResources('AWS::IAM::Role');
|
|
96
|
+
const blocksRoleId = Object.keys(roles).find(k => k.includes('BlocksRole'));
|
|
97
|
+
assert.ok(blocksRoleId, 'expected a role from the BlocksRole construct');
|
|
98
|
+
const blocksRole = roles[blocksRoleId];
|
|
99
|
+
assert.deepStrictEqual(blocksRole.Properties.AssumeRolePolicyDocument.Statement[0], {
|
|
100
|
+
Action: 'sts:AssumeRole',
|
|
101
|
+
Effect: 'Allow',
|
|
102
|
+
Principal: { Service: 'lambda.amazonaws.com' },
|
|
103
|
+
});
|
|
104
|
+
assert.ok(JSON.stringify(blocksRole.Properties.ManagedPolicyArns ?? []).includes('AWSLambdaBasicExecutionRole'), 'BlocksRole should attach AWSLambdaBasicExecutionRole');
|
|
105
|
+
// The Blocks handler references the shared role, not an auto-generated one.
|
|
106
|
+
template.hasResourceProperties('AWS::Lambda::Function', {
|
|
107
|
+
Role: { 'Fn::GetAtt': [blocksRoleId, 'Arn'] },
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
test('a nested block resolves executionRole via the construct-tree walk', async () => {
|
|
111
|
+
const app = new cdk.App();
|
|
112
|
+
const parent = new cdk.Stack(app, 'RoleResolveStack');
|
|
113
|
+
const backend = await BlocksBackend.create(parent, 'Blocks', {
|
|
114
|
+
backendHandlerPath: handlerPath,
|
|
115
|
+
backendCDKPath: sideEffectBackendPath,
|
|
116
|
+
});
|
|
117
|
+
// Build nested Scopes under the backend (outer → inner), the same shape a
|
|
118
|
+
// real Building Block tree has, and grant a uniquely-named marker action to
|
|
119
|
+
// `this.executionRole` from the innermost scope. If the getter's tree-walk
|
|
120
|
+
// failed, it would resolve the wrong role (or throw), and the marker would
|
|
121
|
+
// not land on the backend's shared role.
|
|
122
|
+
// `create()` sets globalThis.CURRENT_BLOCKS_STACK = backend, so a parent-less
|
|
123
|
+
// Scope attaches under the backend (the same way a real backend module's
|
|
124
|
+
// top-level blocks do); `inner` is then nested one level deeper.
|
|
125
|
+
const outer = new Scope('outer');
|
|
126
|
+
const inner = new Scope('inner', { parent: outer });
|
|
127
|
+
// Resolves to the backend's shared role from two levels deep.
|
|
128
|
+
assert.strictEqual(inner.executionRole, backend.executionRole);
|
|
129
|
+
inner.executionRole.addToPrincipalPolicy(new PolicyStatement({ actions: [EXECUTION_ROLE_MARKER_ACTION], resources: ['*'] }));
|
|
130
|
+
// The grant lands on the shared role's default inline policy (AWS::IAM::Policy).
|
|
131
|
+
const template = Template.fromStack(parent);
|
|
132
|
+
template.hasResourceProperties('AWS::IAM::Policy', {
|
|
133
|
+
PolicyDocument: {
|
|
134
|
+
Statement: Match.arrayWith([Match.objectLike({ Action: EXECUTION_ROLE_MARKER_ACTION })]),
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
69
139
|
describe('factory function support', () => {
|
|
70
140
|
test('BlocksBackend.create() calls default export function with the backend instance', async () => {
|
|
71
141
|
const app = new cdk.App();
|
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import { dirname, join } from 'node:path';
|
|
7
7
|
import * as cdk from 'aws-cdk-lib';
|
|
8
8
|
import { BlocksBackend } from './blocks-backend.js';
|
|
9
|
-
import { BlocksStack } from './index.js';
|
|
9
|
+
import { BlocksStack, Scope } from './index.js';
|
|
10
10
|
// Simulate the CDK condition being active (tests import CDK files directly)
|
|
11
11
|
before(() => {
|
|
12
12
|
process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS ?? '') + ' --conditions=cdk';
|
|
@@ -54,6 +54,37 @@ describe('legacy side-effect mode (no default export)', () => {
|
|
|
54
54
|
assert.ok(marker, 'Side-effect-only module should register construct via globalThis.CURRENT_BLOCKS_STACK');
|
|
55
55
|
});
|
|
56
56
|
});
|
|
57
|
+
describe('shared execution role (BlocksStack)', () => {
|
|
58
|
+
// The role synth shape and the Scope.executionRole tree-walk are shared code
|
|
59
|
+
// (setupBlocksInfra + the getter), covered in blocks-backend.test.ts. The only
|
|
60
|
+
// BlocksStack-specific behavior is that its own constructor wires
|
|
61
|
+
// executionRole — a separate code path from BlocksBackend's constructor.
|
|
62
|
+
test('BlocksStack wires executionRole via its constructor', async () => {
|
|
63
|
+
const app = new cdk.App();
|
|
64
|
+
const stack = await BlocksStack.create(app, 'StackRoleStack', {
|
|
65
|
+
backendHandlerPath: handlerPath,
|
|
66
|
+
backendCDKPath: sideEffectBackendPath,
|
|
67
|
+
});
|
|
68
|
+
assert.ok(stack.executionRole, 'BlocksStack should expose a populated .executionRole');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('executionRole globalThis fallback', () => {
|
|
72
|
+
test('resolves via globalThis.CURRENT_BLOCKS_STACK when no owner is in the tree', async () => {
|
|
73
|
+
const app = new cdk.App();
|
|
74
|
+
const stack = await BlocksStack.create(app, 'FallbackStack', {
|
|
75
|
+
backendHandlerPath: handlerPath,
|
|
76
|
+
backendCDKPath: sideEffectBackendPath,
|
|
77
|
+
});
|
|
78
|
+
// A Scope whose construct-tree ancestry has no BlocksStack/BlocksBackend
|
|
79
|
+
// (parented under a plain cdk.Stack) exhausts the tree-walk and falls back
|
|
80
|
+
// to globalThis.CURRENT_BLOCKS_STACK. The `as any` is test plumbing — a
|
|
81
|
+
// plain Stack isn't a ScopeParent, but it IS a valid Construct parent.
|
|
82
|
+
const plainStack = new cdk.Stack(app, 'PlainStack');
|
|
83
|
+
globalThis.CURRENT_BLOCKS_STACK = stack;
|
|
84
|
+
const orphan = new Scope('orphan', { parent: plainStack });
|
|
85
|
+
assert.strictEqual(orphan.executionRole, stack.executionRole, 'fallback resolves to the ambient stack role');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
57
88
|
describe('assertCdkConditionActive', () => {
|
|
58
89
|
test('BlocksStack.create() throws when --conditions=cdk is missing', async () => {
|
|
59
90
|
const origNodeOptions = process.env.NODE_OPTIONS;
|
package/dist/cdk/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export declare class BlocksStack extends cdk.Stack implements BaseBlocksStack {
|
|
|
14
14
|
readonly gateway: cdk.aws_apigateway.RestApi;
|
|
15
15
|
readonly handler: cdk.aws_lambda_nodejs.NodejsFunction;
|
|
16
16
|
readonly backendHandlerPath: string;
|
|
17
|
+
/** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
|
|
18
|
+
readonly executionRole: cdk.aws_iam.IRole;
|
|
17
19
|
private constructor();
|
|
18
20
|
static create(scope: Construct, id: string, props: BlocksStackProps): Promise<BlocksStack>;
|
|
19
21
|
}
|
|
@@ -24,6 +26,17 @@ export declare class Scope extends Construct {
|
|
|
24
26
|
readonly bbVersion?: string;
|
|
25
27
|
constructor(id: string, options?: ScopeOptions);
|
|
26
28
|
get handler(): cdk.aws_lambda_nodejs.NodejsFunction;
|
|
29
|
+
/**
|
|
30
|
+
* The shared IAM role assumed by all Blocks compute. Building Blocks grant
|
|
31
|
+
* their permissions to this role instead of to an individual function's
|
|
32
|
+
* auto-role. CDK's `grant*()` / `addToPrincipalPolicy()` route those grants
|
|
33
|
+
* to the role's default (inline) policy — exactly where they landed on the
|
|
34
|
+
* auto-generated role before.
|
|
35
|
+
*
|
|
36
|
+
* Resolves the same way as {@link handler}: walk up to the owning
|
|
37
|
+
* BlocksStack/BlocksBackend, falling back to the ambient stack.
|
|
38
|
+
*/
|
|
39
|
+
get executionRole(): cdk.aws_iam.IRole;
|
|
27
40
|
get fullId(): string;
|
|
28
41
|
protected buildUserAgentChain(): [string, string][];
|
|
29
42
|
registerClientMiddleware(_packageSpecifier: string): void;
|
package/dist/cdk/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cdk/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,WAAW,IAAI,eAAe,EACnC,KAAK,WAAW,EAChB,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,gCAAgC,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE7F,qBAAa,WAAY,SAAQ,GAAG,CAAC,KAAM,YAAW,eAAe;IACnE,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,OAAO,EAAE,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC;IACpD,SAAgB,OAAO,EAAE,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAAC;IAC9D,SAAgB,kBAAkB,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cdk/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,GAAG,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAGvC,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,WAAW,IAAI,eAAe,EACnC,KAAK,WAAW,EAChB,KAAK,YAAY,EAElB,MAAM,oBAAoB,CAAC;AAK5B,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,gCAAgC,EAAE,MAAM,aAAa,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAE7F,qBAAa,WAAY,SAAQ,GAAG,CAAC,KAAM,YAAW,eAAe;IACnE,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,OAAO,EAAE,GAAG,CAAC,cAAc,CAAC,OAAO,CAAC;IACpD,SAAgB,OAAO,EAAE,GAAG,CAAC,iBAAiB,CAAC,cAAc,CAAC;IAC9D,SAAgB,kBAAkB,EAAE,MAAM,CAAC;IAC3C,yFAAyF;IACzF,SAAgB,aAAa,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC;IAEjD,OAAO;WAeM,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB;CA6B1E;AAED,qBAAa,KAAM,SAAQ,SAAS;IAClC,SAAgB,EAAE,EAAE,MAAM,CAAC;IAC3B,SAAgB,MAAM,EAAE,WAAW,CAAC;IAEpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;gBAEhB,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY;IAO9C,IAAI,OAAO,yCAWV;IAED;;;;;;;;;OASG;IACH,IAAI,aAAa,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,CAUrC;IAED,IAAI,MAAM,IAAI,MAAM,CAEnB;IAED,SAAS,CAAC,mBAAmB,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;IAKnD,wBAAwB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;IACzD,qBAAqB,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI;IACtD,0BAA0B,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;IACrH,IAAI,gBAAgB,IAAI,SAAS,MAAM,EAAE,CAAe;IACxD,IAAI,cAAc,IAAI,SAAS,MAAM,EAAE,CAAe;CACvD"}
|
package/dist/cdk/index.js
CHANGED
|
@@ -20,6 +20,8 @@ export class BlocksStack extends cdk.Stack {
|
|
|
20
20
|
gateway;
|
|
21
21
|
handler;
|
|
22
22
|
backendHandlerPath;
|
|
23
|
+
/** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
|
|
24
|
+
executionRole;
|
|
23
25
|
constructor(scope, id, props) {
|
|
24
26
|
super(scope, id, props);
|
|
25
27
|
this.id = id;
|
|
@@ -30,6 +32,7 @@ export class BlocksStack extends cdk.Stack {
|
|
|
30
32
|
this.handler = infra.handler;
|
|
31
33
|
this.gateway = infra.gateway;
|
|
32
34
|
this.apiUrl = infra.apiUrl;
|
|
35
|
+
this.executionRole = infra.executionRole;
|
|
33
36
|
}
|
|
34
37
|
static async create(scope, id, props) {
|
|
35
38
|
assertCdkConditionActive();
|
|
@@ -80,6 +83,27 @@ export class Scope extends Construct {
|
|
|
80
83
|
// Fallback to globalThis for backward compatibility
|
|
81
84
|
return globalThis.CURRENT_BLOCKS_STACK.handler;
|
|
82
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* The shared IAM role assumed by all Blocks compute. Building Blocks grant
|
|
88
|
+
* their permissions to this role instead of to an individual function's
|
|
89
|
+
* auto-role. CDK's `grant*()` / `addToPrincipalPolicy()` route those grants
|
|
90
|
+
* to the role's default (inline) policy — exactly where they landed on the
|
|
91
|
+
* auto-generated role before.
|
|
92
|
+
*
|
|
93
|
+
* Resolves the same way as {@link handler}: walk up to the owning
|
|
94
|
+
* BlocksStack/BlocksBackend, falling back to the ambient stack.
|
|
95
|
+
*/
|
|
96
|
+
get executionRole() {
|
|
97
|
+
let current = this;
|
|
98
|
+
while (current.node.scope) {
|
|
99
|
+
current = current.node.scope;
|
|
100
|
+
if (current instanceof BlocksStack || current instanceof BlocksBackend) {
|
|
101
|
+
return current.executionRole;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Fallback to globalThis for backward compatibility
|
|
105
|
+
return globalThis.CURRENT_BLOCKS_STACK.executionRole;
|
|
106
|
+
}
|
|
83
107
|
get fullId() {
|
|
84
108
|
return computeScopeFullId(this);
|
|
85
109
|
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CORE_VERSION = "0.1.
|
|
1
|
+
export declare const CORE_VERSION = "0.1.18";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by scripts/generate-version.mjs — do not edit manually
|
|
2
|
-
export const CORE_VERSION = '0.1.
|
|
2
|
+
export const CORE_VERSION = '0.1.18';
|
package/package.json
CHANGED
|
@@ -6,8 +6,10 @@ import assert from 'node:assert';
|
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
7
|
import { dirname, join } from 'node:path';
|
|
8
8
|
import * as cdk from 'aws-cdk-lib';
|
|
9
|
-
import { Template } from 'aws-cdk-lib/assertions';
|
|
9
|
+
import { Template, Match } from 'aws-cdk-lib/assertions';
|
|
10
|
+
import { PolicyStatement } from 'aws-cdk-lib/aws-iam';
|
|
10
11
|
import { BlocksBackend } from './blocks-backend.js';
|
|
12
|
+
import { Scope } from './index.js';
|
|
11
13
|
|
|
12
14
|
// Simulate the CDK condition being active (tests import CDK files directly)
|
|
13
15
|
before(() => {
|
|
@@ -19,6 +21,7 @@ const handlerPath = join(__dirname, '__fixtures__', 'handler.js');
|
|
|
19
21
|
const sideEffectBackendPath = join(__dirname, '__fixtures__', 'side-effect-backend.js');
|
|
20
22
|
const factoryBackendPath = join(__dirname, '__fixtures__', 'factory-backend.js');
|
|
21
23
|
const fullIdConstructBackendPath = join(__dirname, '__fixtures__', 'fullid-construct-backend.js');
|
|
24
|
+
const EXECUTION_ROLE_MARKER_ACTION = 'blocks-test:MarkerAction';
|
|
22
25
|
|
|
23
26
|
describe('ESM cache-busting (multi-stage)', () => {
|
|
24
27
|
test('BlocksBackend.create() with same backendCDKPath but different IDs produces constructs in each', async () => {
|
|
@@ -89,6 +92,92 @@ describe('synth shape (drop into existing stack)', () => {
|
|
|
89
92
|
});
|
|
90
93
|
});
|
|
91
94
|
|
|
95
|
+
describe('shared execution role', () => {
|
|
96
|
+
test('exposes executionRole on the backend', async () => {
|
|
97
|
+
const app = new cdk.App();
|
|
98
|
+
const parent = new cdk.Stack(app, 'RoleSurfaceStack');
|
|
99
|
+
|
|
100
|
+
const backend = await BlocksBackend.create(parent, 'Blocks', {
|
|
101
|
+
backendHandlerPath: handlerPath,
|
|
102
|
+
backendCDKPath: sideEffectBackendPath,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
assert.ok(backend.executionRole, 'BlocksBackend should expose .executionRole');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('synth produces a Lambda-assumable role with basic execution, and the handler uses it', async () => {
|
|
109
|
+
const app = new cdk.App();
|
|
110
|
+
const parent = new cdk.Stack(app, 'RoleSynthStack');
|
|
111
|
+
|
|
112
|
+
await BlocksBackend.create(parent, 'Blocks', {
|
|
113
|
+
backendHandlerPath: handlerPath,
|
|
114
|
+
backendCDKPath: sideEffectBackendPath,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const template = Template.fromStack(parent);
|
|
118
|
+
|
|
119
|
+
// The shared role (logical id derived from the 'BlocksRole' construct id)
|
|
120
|
+
// is assumable by Lambda and carries AWSLambdaBasicExecutionRole (so
|
|
121
|
+
// CloudWatch Logs keep working after swapping off the auto-role). Other
|
|
122
|
+
// roles exist (API Gateway CloudWatch role, config BucketDeployment role),
|
|
123
|
+
// so we target ours by logical id.
|
|
124
|
+
const roles = template.findResources('AWS::IAM::Role');
|
|
125
|
+
const blocksRoleId = Object.keys(roles).find(k => k.includes('BlocksRole'));
|
|
126
|
+
assert.ok(blocksRoleId, 'expected a role from the BlocksRole construct');
|
|
127
|
+
const blocksRole = roles[blocksRoleId];
|
|
128
|
+
assert.deepStrictEqual(blocksRole.Properties.AssumeRolePolicyDocument.Statement[0], {
|
|
129
|
+
Action: 'sts:AssumeRole',
|
|
130
|
+
Effect: 'Allow',
|
|
131
|
+
Principal: { Service: 'lambda.amazonaws.com' },
|
|
132
|
+
});
|
|
133
|
+
assert.ok(
|
|
134
|
+
JSON.stringify(blocksRole.Properties.ManagedPolicyArns ?? []).includes('AWSLambdaBasicExecutionRole'),
|
|
135
|
+
'BlocksRole should attach AWSLambdaBasicExecutionRole',
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
// The Blocks handler references the shared role, not an auto-generated one.
|
|
139
|
+
template.hasResourceProperties('AWS::Lambda::Function', {
|
|
140
|
+
Role: { 'Fn::GetAtt': [blocksRoleId, 'Arn'] },
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('a nested block resolves executionRole via the construct-tree walk', async () => {
|
|
145
|
+
const app = new cdk.App();
|
|
146
|
+
const parent = new cdk.Stack(app, 'RoleResolveStack');
|
|
147
|
+
|
|
148
|
+
const backend = await BlocksBackend.create(parent, 'Blocks', {
|
|
149
|
+
backendHandlerPath: handlerPath,
|
|
150
|
+
backendCDKPath: sideEffectBackendPath,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Build nested Scopes under the backend (outer → inner), the same shape a
|
|
154
|
+
// real Building Block tree has, and grant a uniquely-named marker action to
|
|
155
|
+
// `this.executionRole` from the innermost scope. If the getter's tree-walk
|
|
156
|
+
// failed, it would resolve the wrong role (or throw), and the marker would
|
|
157
|
+
// not land on the backend's shared role.
|
|
158
|
+
// `create()` sets globalThis.CURRENT_BLOCKS_STACK = backend, so a parent-less
|
|
159
|
+
// Scope attaches under the backend (the same way a real backend module's
|
|
160
|
+
// top-level blocks do); `inner` is then nested one level deeper.
|
|
161
|
+
const outer = new Scope('outer');
|
|
162
|
+
const inner = new Scope('inner', { parent: outer });
|
|
163
|
+
|
|
164
|
+
// Resolves to the backend's shared role from two levels deep.
|
|
165
|
+
assert.strictEqual(inner.executionRole, backend.executionRole);
|
|
166
|
+
|
|
167
|
+
inner.executionRole.addToPrincipalPolicy(
|
|
168
|
+
new PolicyStatement({ actions: [EXECUTION_ROLE_MARKER_ACTION], resources: ['*'] }),
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
// The grant lands on the shared role's default inline policy (AWS::IAM::Policy).
|
|
172
|
+
const template = Template.fromStack(parent);
|
|
173
|
+
template.hasResourceProperties('AWS::IAM::Policy', {
|
|
174
|
+
PolicyDocument: {
|
|
175
|
+
Statement: Match.arrayWith([Match.objectLike({ Action: EXECUTION_ROLE_MARKER_ACTION })]),
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
92
181
|
describe('factory function support', () => {
|
|
93
182
|
test('BlocksBackend.create() calls default export function with the backend instance', async () => {
|
|
94
183
|
const app = new cdk.App();
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import * as cdk from 'aws-cdk-lib';
|
|
5
5
|
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs';
|
|
6
6
|
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
|
|
7
|
+
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
7
8
|
import { CfnGroup } from 'aws-cdk-lib/aws-resourcegroups';
|
|
8
9
|
import { Construct } from 'constructs';
|
|
9
10
|
import { pathToFileURL } from 'node:url';
|
|
@@ -48,10 +49,29 @@ export interface BlocksBackendProps {
|
|
|
48
49
|
|
|
49
50
|
/** Shared infra setup — creates Lambda + API Gateway on the given scope. */
|
|
50
51
|
export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id?: string) {
|
|
52
|
+
// ── Shared execution role ──────────────────────────────────────────────
|
|
53
|
+
// A single IAM role that every Building Block grants to. Provisioned here so
|
|
54
|
+
// it exists before the backend module is imported (Building Blocks reach it
|
|
55
|
+
// via `scope.executionRole`). Block grants sit on the role's default (inline)
|
|
56
|
+
// policy, exactly as they did on the auto-generated NodejsFunction role.
|
|
57
|
+
//
|
|
58
|
+
// AWSLambdaBasicExecutionRole is attached explicitly because the auto-role
|
|
59
|
+
// included it by default — omitting it would silently break CloudWatch Logs.
|
|
60
|
+
const executionRole = new iam.Role(scope, 'BlocksRole', {
|
|
61
|
+
// CompositePrincipal (rather than a bare ServicePrincipal) so additional
|
|
62
|
+
// compute types can assume this same shared role as they are introduced
|
|
63
|
+
// (e.g. ECS tasks via ecs-tasks.amazonaws.com), by adding principals here.
|
|
64
|
+
assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('lambda.amazonaws.com')),
|
|
65
|
+
managedPolicies: [
|
|
66
|
+
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
|
|
51
70
|
const handler = new lambda.NodejsFunction(scope, 'Handler', {
|
|
52
71
|
entry: props.backendHandlerPath,
|
|
53
72
|
runtime: DEFAULT_NODE_RUNTIME,
|
|
54
73
|
handler: 'handler',
|
|
74
|
+
role: executionRole,
|
|
55
75
|
memorySize: 2048,
|
|
56
76
|
timeout: cdk.Duration.seconds(60 * 15),
|
|
57
77
|
environment: {
|
|
@@ -152,7 +172,7 @@ export function setupBlocksInfra(scope: Construct, props: BlocksBackendProps, id
|
|
|
152
172
|
|
|
153
173
|
registerBuiltinRoutes();
|
|
154
174
|
|
|
155
|
-
return { handler, gateway: api, apiUrl: `${api.url}${BLOCKS_RPC_PREFIX.slice(1)}
|
|
175
|
+
return { handler, gateway: api, apiUrl: `${api.url}${BLOCKS_RPC_PREFIX.slice(1)}`, executionRole };
|
|
156
176
|
}
|
|
157
177
|
|
|
158
178
|
/**
|
|
@@ -177,6 +197,8 @@ export class BlocksBackend extends Construct {
|
|
|
177
197
|
public readonly gateway: apigateway.RestApi;
|
|
178
198
|
public readonly handler: cdk.aws_lambda_nodejs.NodejsFunction;
|
|
179
199
|
public readonly backendHandlerPath: string;
|
|
200
|
+
/** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
|
|
201
|
+
public readonly executionRole: iam.IRole;
|
|
180
202
|
|
|
181
203
|
/**
|
|
182
204
|
* The fullId used by child Scopes to compute their env var names,
|
|
@@ -221,6 +243,7 @@ export class BlocksBackend extends Construct {
|
|
|
221
243
|
this.handler = infra.handler;
|
|
222
244
|
this.gateway = infra.gateway;
|
|
223
245
|
this.apiUrl = infra.apiUrl;
|
|
246
|
+
this.executionRole = infra.executionRole;
|
|
224
247
|
|
|
225
248
|
// Override BLOCKS_STACK_NAME to include the parent stack name so runtime
|
|
226
249
|
// resource lookups (DynamoDB table names) match the CDK-time fullId
|
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
7
7
|
import { dirname, join } from 'node:path';
|
|
8
8
|
import * as cdk from 'aws-cdk-lib';
|
|
9
9
|
import { BlocksBackend } from './blocks-backend.js';
|
|
10
|
-
import { BlocksStack } from './index.js';
|
|
10
|
+
import { BlocksStack, Scope } from './index.js';
|
|
11
11
|
|
|
12
12
|
// Simulate the CDK condition being active (tests import CDK files directly)
|
|
13
13
|
before(() => {
|
|
@@ -78,6 +78,46 @@ describe('legacy side-effect mode (no default export)', () => {
|
|
|
78
78
|
});
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
describe('shared execution role (BlocksStack)', () => {
|
|
82
|
+
// The role synth shape and the Scope.executionRole tree-walk are shared code
|
|
83
|
+
// (setupBlocksInfra + the getter), covered in blocks-backend.test.ts. The only
|
|
84
|
+
// BlocksStack-specific behavior is that its own constructor wires
|
|
85
|
+
// executionRole — a separate code path from BlocksBackend's constructor.
|
|
86
|
+
test('BlocksStack wires executionRole via its constructor', async () => {
|
|
87
|
+
const app = new cdk.App();
|
|
88
|
+
const stack = await BlocksStack.create(app, 'StackRoleStack', {
|
|
89
|
+
backendHandlerPath: handlerPath,
|
|
90
|
+
backendCDKPath: sideEffectBackendPath,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
assert.ok(stack.executionRole, 'BlocksStack should expose a populated .executionRole');
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe('executionRole globalThis fallback', () => {
|
|
98
|
+
test('resolves via globalThis.CURRENT_BLOCKS_STACK when no owner is in the tree', async () => {
|
|
99
|
+
const app = new cdk.App();
|
|
100
|
+
const stack = await BlocksStack.create(app, 'FallbackStack', {
|
|
101
|
+
backendHandlerPath: handlerPath,
|
|
102
|
+
backendCDKPath: sideEffectBackendPath,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// A Scope whose construct-tree ancestry has no BlocksStack/BlocksBackend
|
|
106
|
+
// (parented under a plain cdk.Stack) exhausts the tree-walk and falls back
|
|
107
|
+
// to globalThis.CURRENT_BLOCKS_STACK. The `as any` is test plumbing — a
|
|
108
|
+
// plain Stack isn't a ScopeParent, but it IS a valid Construct parent.
|
|
109
|
+
const plainStack = new cdk.Stack(app, 'PlainStack');
|
|
110
|
+
(globalThis as any).CURRENT_BLOCKS_STACK = stack;
|
|
111
|
+
const orphan = new Scope('orphan', { parent: plainStack as any });
|
|
112
|
+
|
|
113
|
+
assert.strictEqual(
|
|
114
|
+
orphan.executionRole,
|
|
115
|
+
stack.executionRole,
|
|
116
|
+
'fallback resolves to the ambient stack role',
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
81
121
|
describe('assertCdkConditionActive', () => {
|
|
82
122
|
test('BlocksStack.create() throws when --conditions=cdk is missing', async () => {
|
|
83
123
|
const origNodeOptions = process.env.NODE_OPTIONS;
|
package/src/cdk/index.ts
CHANGED
|
@@ -30,6 +30,8 @@ export class BlocksStack extends cdk.Stack implements BaseBlocksStack {
|
|
|
30
30
|
public readonly gateway: cdk.aws_apigateway.RestApi;
|
|
31
31
|
public readonly handler: cdk.aws_lambda_nodejs.NodejsFunction;
|
|
32
32
|
public readonly backendHandlerPath: string;
|
|
33
|
+
/** Shared IAM role assumed by all Blocks compute. Building Blocks grant to this role. */
|
|
34
|
+
public readonly executionRole: cdk.aws_iam.IRole;
|
|
33
35
|
|
|
34
36
|
private constructor(scope: Construct, id: string, props: BlocksStackProps) {
|
|
35
37
|
super(scope, id, props);
|
|
@@ -43,6 +45,7 @@ export class BlocksStack extends cdk.Stack implements BaseBlocksStack {
|
|
|
43
45
|
this.handler = infra.handler;
|
|
44
46
|
this.gateway = infra.gateway;
|
|
45
47
|
this.apiUrl = infra.apiUrl;
|
|
48
|
+
this.executionRole = infra.executionRole;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
static async create(scope: Construct, id: string, props: BlocksStackProps) {
|
|
@@ -103,6 +106,28 @@ export class Scope extends Construct {
|
|
|
103
106
|
return ((globalThis as any).CURRENT_BLOCKS_STACK as { handler: cdk.aws_lambda_nodejs.NodejsFunction }).handler;
|
|
104
107
|
}
|
|
105
108
|
|
|
109
|
+
/**
|
|
110
|
+
* The shared IAM role assumed by all Blocks compute. Building Blocks grant
|
|
111
|
+
* their permissions to this role instead of to an individual function's
|
|
112
|
+
* auto-role. CDK's `grant*()` / `addToPrincipalPolicy()` route those grants
|
|
113
|
+
* to the role's default (inline) policy — exactly where they landed on the
|
|
114
|
+
* auto-generated role before.
|
|
115
|
+
*
|
|
116
|
+
* Resolves the same way as {@link handler}: walk up to the owning
|
|
117
|
+
* BlocksStack/BlocksBackend, falling back to the ambient stack.
|
|
118
|
+
*/
|
|
119
|
+
get executionRole(): cdk.aws_iam.IRole {
|
|
120
|
+
let current: Construct = this;
|
|
121
|
+
while (current.node.scope) {
|
|
122
|
+
current = current.node.scope as Construct;
|
|
123
|
+
if (current instanceof BlocksStack || current instanceof BlocksBackend) {
|
|
124
|
+
return current.executionRole;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Fallback to globalThis for backward compatibility
|
|
128
|
+
return ((globalThis as any).CURRENT_BLOCKS_STACK as { executionRole: cdk.aws_iam.IRole }).executionRole;
|
|
129
|
+
}
|
|
130
|
+
|
|
106
131
|
get fullId(): string {
|
|
107
132
|
return computeScopeFullId(this);
|
|
108
133
|
}
|
package/src/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by scripts/generate-version.mjs — do not edit manually
|
|
2
|
-
export const CORE_VERSION = '0.1.
|
|
2
|
+
export const CORE_VERSION = '0.1.18';
|