puls-dev 0.1.9 → 0.2.1
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 +8 -8
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -7
- package/dist/providers/aws/api.d.ts +4 -0
- package/dist/providers/aws/api.js +4 -0
- package/dist/providers/aws/cloudwatch.d.ts +44 -0
- package/dist/providers/aws/cloudwatch.js +205 -0
- package/dist/providers/aws/cloudwatch.test.d.ts +1 -0
- package/dist/providers/aws/cloudwatch.test.js +224 -0
- package/dist/providers/aws/fargate.d.ts +2 -0
- package/dist/providers/aws/fargate.js +6 -0
- package/dist/providers/aws/iam.d.ts +52 -0
- package/dist/providers/aws/iam.js +307 -0
- package/dist/providers/aws/iam.test.d.ts +1 -0
- package/dist/providers/aws/iam.test.js +367 -0
- package/dist/providers/aws/index.d.ts +8 -0
- package/dist/providers/aws/index.js +8 -0
- package/dist/providers/aws/lambda.d.ts +3 -1
- package/dist/providers/aws/lambda.js +17 -8
- package/dist/providers/aws/lambda.test.d.ts +1 -0
- package/dist/providers/aws/lambda.test.js +189 -0
- package/dist/providers/aws/rds.d.ts +1 -0
- package/dist/providers/aws/rds.js +4 -1
- package/dist/providers/aws/route53.d.ts +1 -1
- package/dist/providers/aws/route53.js +20 -12
- package/dist/providers/aws/route53.test.d.ts +1 -0
- package/dist/providers/aws/route53.test.js +229 -0
- package/dist/providers/aws/s3.d.ts +3 -0
- package/dist/providers/aws/s3.js +65 -3
- package/dist/providers/aws/s3.test.d.ts +1 -0
- package/dist/providers/aws/s3.test.js +172 -0
- package/dist/providers/aws/sns.d.ts +22 -0
- package/dist/providers/aws/sns.js +146 -0
- package/dist/providers/aws/sns.test.d.ts +1 -0
- package/dist/providers/aws/sns.test.js +162 -0
- package/dist/providers/do/api.js +5 -1
- package/dist/providers/do/certificate.test.d.ts +1 -0
- package/dist/providers/do/certificate.test.js +133 -0
- package/dist/providers/do/domain.d.ts +12 -1
- package/dist/providers/do/domain.js +129 -13
- package/dist/providers/do/domain.test.d.ts +1 -0
- package/dist/providers/do/domain.test.js +200 -0
- package/dist/providers/do/droplet.js +2 -2
- package/dist/providers/do/droplet.test.d.ts +1 -0
- package/dist/providers/do/droplet.test.js +265 -0
- package/dist/providers/do/firewall.test.d.ts +1 -0
- package/dist/providers/do/firewall.test.js +176 -0
- package/dist/providers/do/index.d.ts +1 -0
- package/dist/providers/do/index.js +1 -0
- package/dist/providers/do/load_balancer.d.ts +39 -5
- package/dist/providers/do/load_balancer.js +272 -30
- package/dist/providers/do/load_balancer.test.d.ts +1 -0
- package/dist/providers/do/load_balancer.test.js +269 -0
- package/dist/providers/firebase/api.js +2 -2
- package/dist/providers/firebase/functions.d.ts +1 -0
- package/dist/providers/firebase/functions.js +24 -10
- package/dist/providers/firebase/functions.test.d.ts +1 -0
- package/dist/providers/firebase/functions.test.js +297 -0
- package/dist/providers/firebase/hosting.js +5 -5
- package/dist/providers/firebase/hosting.test.d.ts +1 -0
- package/dist/providers/firebase/hosting.test.js +181 -0
- package/dist/providers/proxmox/index.d.ts +1 -0
- package/dist/providers/proxmox/index.js +1 -0
- package/dist/providers/proxmox/vm.d.ts +2 -1
- package/dist/providers/proxmox/vm.js +39 -53
- package/dist/providers/proxmox/vm.test.d.ts +1 -0
- package/dist/providers/proxmox/vm.test.js +155 -0
- package/dist/types/aws.d.ts +11 -0
- package/package.json +105 -6
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { GetRoleCommand, CreateRoleCommand, UpdateRoleCommand, DeleteRoleCommand, UpdateAssumeRolePolicyCommand, AttachRolePolicyCommand, DetachRolePolicyCommand, ListAttachedRolePoliciesCommand, PutRolePolicyCommand, DeleteRolePolicyCommand, ListRolePoliciesCommand, CreatePolicyCommand, DeletePolicyCommand, ListPoliciesCommand, CreatePolicyVersionCommand, DeletePolicyVersionCommand, ListPolicyVersionsCommand, } from "@aws-sdk/client-iam";
|
|
2
|
+
import { BaseBuilder } from "../../core/resource.js";
|
|
3
|
+
import { Output } from "../../core/output.js";
|
|
4
|
+
import { getIAMClient } from "./api.js";
|
|
5
|
+
const DEFAULT_ASSUME_ROLE_POLICY = {
|
|
6
|
+
Version: "2012-10-17",
|
|
7
|
+
Statement: [
|
|
8
|
+
{
|
|
9
|
+
Effect: "Allow",
|
|
10
|
+
Principal: { Service: "lambda.amazonaws.com" },
|
|
11
|
+
Action: "sts:AssumeRole",
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
};
|
|
15
|
+
export class IAMPolicyBuilder extends BaseBuilder {
|
|
16
|
+
out = {
|
|
17
|
+
arn: new Output(),
|
|
18
|
+
};
|
|
19
|
+
_document;
|
|
20
|
+
_description;
|
|
21
|
+
_path = "/";
|
|
22
|
+
resolvedArn = null;
|
|
23
|
+
constructor(name) {
|
|
24
|
+
super(name);
|
|
25
|
+
this.discoveryPromise = this.discoverPolicy(name);
|
|
26
|
+
}
|
|
27
|
+
async discoverPolicy(name) {
|
|
28
|
+
try {
|
|
29
|
+
const iam = getIAMClient();
|
|
30
|
+
const result = await iam.send(new ListPoliciesCommand({ Scope: "Local", OnlyAttached: false, MaxItems: 100 }));
|
|
31
|
+
const match = (result.Policies ?? []).find((p) => p.PolicyName === name);
|
|
32
|
+
if (match) {
|
|
33
|
+
this.resolvedArn = match.Arn ?? null;
|
|
34
|
+
if (this.resolvedArn) {
|
|
35
|
+
this.out.arn.resolve(this.resolvedArn);
|
|
36
|
+
}
|
|
37
|
+
return match;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
if (e.name === "CredentialsProviderError")
|
|
43
|
+
return null;
|
|
44
|
+
throw e;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
document(doc) {
|
|
48
|
+
this._document = doc;
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
description(desc) {
|
|
52
|
+
this._description = desc;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
path(p) {
|
|
56
|
+
this._path = p;
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
async deploy() {
|
|
60
|
+
const dryRun = this.isDryRunActive();
|
|
61
|
+
const existing = await this.discoveryPromise;
|
|
62
|
+
const iam = getIAMClient();
|
|
63
|
+
console.log(`\n🔐 Finalizing IAM Policy "${this.name}"...`);
|
|
64
|
+
if (!this._document) {
|
|
65
|
+
throw new Error(`[IAMPolicy:${this.name}] .document() is required`);
|
|
66
|
+
}
|
|
67
|
+
if (dryRun) {
|
|
68
|
+
console.log(` 📝 [PLAN] ${existing ? "Update" : "Create"} IAM policy "${this.name}"`);
|
|
69
|
+
this.resolvedArn = existing?.Arn ?? `arn:aws:iam::000000000000:policy/DRYRUN-${this.name}`;
|
|
70
|
+
this.out.arn.resolve(this.resolvedArn);
|
|
71
|
+
return { name: this.name, arn: this.resolvedArn };
|
|
72
|
+
}
|
|
73
|
+
if (!existing) {
|
|
74
|
+
const result = await iam.send(new CreatePolicyCommand({
|
|
75
|
+
PolicyName: this.name,
|
|
76
|
+
PolicyDocument: JSON.stringify(this._document),
|
|
77
|
+
Description: this._description,
|
|
78
|
+
Path: this._path,
|
|
79
|
+
}));
|
|
80
|
+
this.resolvedArn = result.Policy.Arn;
|
|
81
|
+
this.out.arn.resolve(this.resolvedArn);
|
|
82
|
+
console.log(`🚀 Created IAM Policy "${this.name}" (arn=${this.resolvedArn})`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
this.resolvedArn = existing.Arn;
|
|
86
|
+
this.out.arn.resolve(this.resolvedArn);
|
|
87
|
+
// Handle AWS 5-version limit
|
|
88
|
+
const versions = await iam.send(new ListPolicyVersionsCommand({ PolicyArn: this.resolvedArn }));
|
|
89
|
+
if (versions.Versions && versions.Versions.length >= 5) {
|
|
90
|
+
const nonDefault = versions.Versions.filter((v) => !v.IsDefaultVersion);
|
|
91
|
+
if (nonDefault.length > 0) {
|
|
92
|
+
nonDefault.sort((a, b) => new Date(a.CreateDate).getTime() - new Date(b.CreateDate).getTime());
|
|
93
|
+
const oldest = nonDefault[0];
|
|
94
|
+
await iam.send(new DeletePolicyVersionCommand({
|
|
95
|
+
PolicyArn: this.resolvedArn,
|
|
96
|
+
VersionId: oldest.VersionId,
|
|
97
|
+
}));
|
|
98
|
+
console.log(` 🧹 Pruned oldest IAM policy version: ${oldest.VersionId}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
await iam.send(new CreatePolicyVersionCommand({
|
|
102
|
+
PolicyArn: this.resolvedArn,
|
|
103
|
+
PolicyDocument: JSON.stringify(this._document),
|
|
104
|
+
SetAsDefault: true,
|
|
105
|
+
}));
|
|
106
|
+
console.log(` ✅ Updated IAM Policy "${this.name}" (created new default version)`);
|
|
107
|
+
}
|
|
108
|
+
return { name: this.name, arn: this.resolvedArn };
|
|
109
|
+
}
|
|
110
|
+
async destroy() {
|
|
111
|
+
const dryRun = this.isDryRunActive();
|
|
112
|
+
const existing = await this.discoveryPromise;
|
|
113
|
+
const iam = getIAMClient();
|
|
114
|
+
console.log(`\n🗑️ Destroying IAM Policy "${this.name}"...`);
|
|
115
|
+
if (!existing || !existing.Arn) {
|
|
116
|
+
console.log(` ✅ IAM Policy "${this.name}" does not exist - nothing to do`);
|
|
117
|
+
return { destroyed: this.name };
|
|
118
|
+
}
|
|
119
|
+
const policyArn = existing.Arn;
|
|
120
|
+
if (dryRun) {
|
|
121
|
+
console.log(` 📝 [PLAN] Delete IAM policy "${this.name}"`);
|
|
122
|
+
return { destroyed: this.name };
|
|
123
|
+
}
|
|
124
|
+
// Must delete all non-default versions before deleting policy
|
|
125
|
+
const versions = await iam.send(new ListPolicyVersionsCommand({ PolicyArn: policyArn }));
|
|
126
|
+
for (const v of versions.Versions ?? []) {
|
|
127
|
+
if (!v.IsDefaultVersion) {
|
|
128
|
+
await iam.send(new DeletePolicyVersionCommand({
|
|
129
|
+
PolicyArn: policyArn,
|
|
130
|
+
VersionId: v.VersionId,
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
await iam.send(new DeletePolicyCommand({ PolicyArn: policyArn }));
|
|
135
|
+
console.log(` ✅ Deleted IAM Policy "${this.name}"`);
|
|
136
|
+
return { destroyed: this.name };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export class IAMRoleBuilder extends BaseBuilder {
|
|
140
|
+
out = {
|
|
141
|
+
arn: new Output(),
|
|
142
|
+
name: new Output(),
|
|
143
|
+
};
|
|
144
|
+
_assumeRolePolicy = DEFAULT_ASSUME_ROLE_POLICY;
|
|
145
|
+
_managedPolicies = [];
|
|
146
|
+
_inlinePolicies = {};
|
|
147
|
+
_description;
|
|
148
|
+
_path = "/";
|
|
149
|
+
_maxSessionDuration;
|
|
150
|
+
resolvedArn = null;
|
|
151
|
+
constructor(name) {
|
|
152
|
+
super(name);
|
|
153
|
+
this.discoveryPromise = this.discoverRole(name);
|
|
154
|
+
}
|
|
155
|
+
async discoverRole(name) {
|
|
156
|
+
try {
|
|
157
|
+
const iam = getIAMClient();
|
|
158
|
+
const result = await iam.send(new GetRoleCommand({ RoleName: name }));
|
|
159
|
+
this.resolvedArn = result.Role.Arn;
|
|
160
|
+
this.out.arn.resolve(this.resolvedArn);
|
|
161
|
+
this.out.name.resolve(name);
|
|
162
|
+
return result.Role;
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
if (e.name === "NoSuchEntityException")
|
|
166
|
+
return null;
|
|
167
|
+
if (e.name === "CredentialsProviderError")
|
|
168
|
+
return null;
|
|
169
|
+
throw e;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
assumeRolePolicy(doc) {
|
|
173
|
+
this._assumeRolePolicy = doc;
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
attach(policyArnOrBuilder) {
|
|
177
|
+
this._managedPolicies.push(policyArnOrBuilder);
|
|
178
|
+
return this;
|
|
179
|
+
}
|
|
180
|
+
inlinePolicy(name, doc) {
|
|
181
|
+
this._inlinePolicies[name] = doc;
|
|
182
|
+
return this;
|
|
183
|
+
}
|
|
184
|
+
description(desc) {
|
|
185
|
+
this._description = desc;
|
|
186
|
+
return this;
|
|
187
|
+
}
|
|
188
|
+
path(p) {
|
|
189
|
+
this._path = p;
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
maxSessionDuration(seconds) {
|
|
193
|
+
this._maxSessionDuration = seconds;
|
|
194
|
+
return this;
|
|
195
|
+
}
|
|
196
|
+
async deploy() {
|
|
197
|
+
const dryRun = this.isDryRunActive();
|
|
198
|
+
const existing = await this.discoveryPromise;
|
|
199
|
+
const iam = getIAMClient();
|
|
200
|
+
console.log(`\n⚡ Finalizing IAM Role "${this.name}"...`);
|
|
201
|
+
if (dryRun) {
|
|
202
|
+
console.log(` 📝 [PLAN] ${existing ? "Update" : "Create"} IAM role "${this.name}"`);
|
|
203
|
+
this.resolvedArn = existing?.Arn ?? `arn:aws:iam::000000000000:role/DRYRUN-${this.name}`;
|
|
204
|
+
this.out.arn.resolve(this.resolvedArn);
|
|
205
|
+
this.out.name.resolve(this.name);
|
|
206
|
+
return { name: this.name, arn: this.resolvedArn };
|
|
207
|
+
}
|
|
208
|
+
if (!existing) {
|
|
209
|
+
const result = await iam.send(new CreateRoleCommand({
|
|
210
|
+
RoleName: this.name,
|
|
211
|
+
AssumeRolePolicyDocument: JSON.stringify(this._assumeRolePolicy),
|
|
212
|
+
Description: this._description,
|
|
213
|
+
Path: this._path,
|
|
214
|
+
MaxSessionDuration: this._maxSessionDuration,
|
|
215
|
+
}));
|
|
216
|
+
this.resolvedArn = result.Role.Arn;
|
|
217
|
+
this.out.arn.resolve(this.resolvedArn);
|
|
218
|
+
this.out.name.resolve(this.name);
|
|
219
|
+
console.log(`🚀 Created IAM Role "${this.name}" (arn=${this.resolvedArn})`);
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
this.resolvedArn = existing.Arn;
|
|
223
|
+
this.out.arn.resolve(this.resolvedArn);
|
|
224
|
+
this.out.name.resolve(this.name);
|
|
225
|
+
await iam.send(new UpdateAssumeRolePolicyCommand({
|
|
226
|
+
RoleName: this.name,
|
|
227
|
+
PolicyDocument: JSON.stringify(this._assumeRolePolicy),
|
|
228
|
+
}));
|
|
229
|
+
await iam.send(new UpdateRoleCommand({
|
|
230
|
+
RoleName: this.name,
|
|
231
|
+
Description: this._description || existing.Description,
|
|
232
|
+
MaxSessionDuration: this._maxSessionDuration || existing.MaxSessionDuration,
|
|
233
|
+
}));
|
|
234
|
+
console.log(` ✅ Updated IAM Role "${this.name}" configuration`);
|
|
235
|
+
}
|
|
236
|
+
// Resolve requested managed policies
|
|
237
|
+
const requestedArns = [];
|
|
238
|
+
for (const policy of this._managedPolicies) {
|
|
239
|
+
if (policy instanceof IAMPolicyBuilder) {
|
|
240
|
+
requestedArns.push(await policy.out.arn.get());
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
requestedArns.push(policy);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Sync managed policies
|
|
247
|
+
const attached = await iam.send(new ListAttachedRolePoliciesCommand({ RoleName: this.name }));
|
|
248
|
+
const attachedArns = (attached.AttachedPolicies ?? []).map((p) => p.PolicyArn);
|
|
249
|
+
for (const arn of attachedArns) {
|
|
250
|
+
if (!requestedArns.includes(arn)) {
|
|
251
|
+
await iam.send(new DetachRolePolicyCommand({ RoleName: this.name, PolicyArn: arn }));
|
|
252
|
+
console.log(` ➖ Detached policy ${arn} from role ${this.name}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
for (const arn of requestedArns) {
|
|
256
|
+
if (!attachedArns.includes(arn)) {
|
|
257
|
+
await iam.send(new AttachRolePolicyCommand({ RoleName: this.name, PolicyArn: arn }));
|
|
258
|
+
console.log(` ➕ Attached policy ${arn} to role ${this.name}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Sync inline policies
|
|
262
|
+
const inline = await iam.send(new ListRolePoliciesCommand({ RoleName: this.name }));
|
|
263
|
+
const inlineNames = inline.PolicyNames ?? [];
|
|
264
|
+
for (const pName of inlineNames) {
|
|
265
|
+
if (!this._inlinePolicies[pName]) {
|
|
266
|
+
await iam.send(new DeleteRolePolicyCommand({ RoleName: this.name, PolicyName: pName }));
|
|
267
|
+
console.log(` ➖ Deleted inline policy ${pName} from role ${this.name}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
for (const [pName, doc] of Object.entries(this._inlinePolicies)) {
|
|
271
|
+
await iam.send(new PutRolePolicyCommand({
|
|
272
|
+
RoleName: this.name,
|
|
273
|
+
PolicyName: pName,
|
|
274
|
+
PolicyDocument: JSON.stringify(doc),
|
|
275
|
+
}));
|
|
276
|
+
console.log(` ➕ Applied inline policy ${pName} to role ${this.name}`);
|
|
277
|
+
}
|
|
278
|
+
return { name: this.name, arn: this.resolvedArn };
|
|
279
|
+
}
|
|
280
|
+
async destroy() {
|
|
281
|
+
const dryRun = this.isDryRunActive();
|
|
282
|
+
const existing = await this.discoveryPromise;
|
|
283
|
+
const iam = getIAMClient();
|
|
284
|
+
console.log(`\n🗑️ Destroying IAM Role "${this.name}"...`);
|
|
285
|
+
if (!existing) {
|
|
286
|
+
console.log(` ✅ IAM Role "${this.name}" does not exist - nothing to do`);
|
|
287
|
+
return { destroyed: this.name };
|
|
288
|
+
}
|
|
289
|
+
if (dryRun) {
|
|
290
|
+
console.log(` 📝 [PLAN] Delete IAM role "${this.name}"`);
|
|
291
|
+
return { destroyed: this.name };
|
|
292
|
+
}
|
|
293
|
+
// Must delete all inline policies first
|
|
294
|
+
const inline = await iam.send(new ListRolePoliciesCommand({ RoleName: this.name }));
|
|
295
|
+
for (const pName of inline.PolicyNames ?? []) {
|
|
296
|
+
await iam.send(new DeleteRolePolicyCommand({ RoleName: this.name, PolicyName: pName }));
|
|
297
|
+
}
|
|
298
|
+
// Must detach all attached managed policies
|
|
299
|
+
const attached = await iam.send(new ListAttachedRolePoliciesCommand({ RoleName: this.name }));
|
|
300
|
+
for (const p of attached.AttachedPolicies ?? []) {
|
|
301
|
+
await iam.send(new DetachRolePolicyCommand({ RoleName: this.name, PolicyArn: p.PolicyArn }));
|
|
302
|
+
}
|
|
303
|
+
await iam.send(new DeleteRoleCommand({ RoleName: this.name }));
|
|
304
|
+
console.log(` ✅ Deleted IAM Role "${this.name}"`);
|
|
305
|
+
return { destroyed: this.name };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import { test, describe, beforeEach, afterEach, mock } from 'node:test';
|
|
2
|
+
import assert from 'assert';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import { IAMClient } from '@aws-sdk/client-iam';
|
|
5
|
+
import { LambdaClient } from '@aws-sdk/client-lambda';
|
|
6
|
+
import { Config } from '../../core/config.js';
|
|
7
|
+
import { IAMRoleBuilder, IAMPolicyBuilder } from './iam.js';
|
|
8
|
+
import { LambdaBuilder } from './lambda.js';
|
|
9
|
+
describe('AWS IAM Builders Unit Tests', () => {
|
|
10
|
+
let originalSend;
|
|
11
|
+
let iamCalls = [];
|
|
12
|
+
let mockIamResponses = {};
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
Config.set({
|
|
15
|
+
dryRun: false,
|
|
16
|
+
providers: {
|
|
17
|
+
aws: { region: 'us-east-1' }
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
iamCalls = [];
|
|
21
|
+
mockIamResponses = {};
|
|
22
|
+
originalSend = IAMClient.prototype.send;
|
|
23
|
+
// FS Mocking to return a fake zip buffer when reading the code package
|
|
24
|
+
mock.method(fs, 'readFileSync', () => {
|
|
25
|
+
return Buffer.from('mock-zip-binary-payload');
|
|
26
|
+
});
|
|
27
|
+
// Intercept all IAM command sends
|
|
28
|
+
IAMClient.prototype.send = async function (command) {
|
|
29
|
+
const commandName = command.constructor.name;
|
|
30
|
+
const input = command.input;
|
|
31
|
+
iamCalls.push({ commandName, input });
|
|
32
|
+
if (mockIamResponses[commandName]) {
|
|
33
|
+
const handler = mockIamResponses[commandName];
|
|
34
|
+
if (typeof handler === 'function')
|
|
35
|
+
return handler(input);
|
|
36
|
+
if (handler instanceof Error)
|
|
37
|
+
throw handler;
|
|
38
|
+
return handler;
|
|
39
|
+
}
|
|
40
|
+
return {};
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
IAMClient.prototype.send = originalSend;
|
|
45
|
+
mock.restoreAll();
|
|
46
|
+
});
|
|
47
|
+
describe('IAMPolicyBuilder Tests', () => {
|
|
48
|
+
test('gracefully handles discovery when policy does not exist', async () => {
|
|
49
|
+
mockIamResponses['ListPoliciesCommand'] = { Policies: [] };
|
|
50
|
+
const builder = new IAMPolicyBuilder('my-policy');
|
|
51
|
+
const discoveryResult = await builder.discoveryPromise;
|
|
52
|
+
assert.strictEqual(discoveryResult, null);
|
|
53
|
+
assert.strictEqual(iamCalls.length, 1);
|
|
54
|
+
assert.strictEqual(iamCalls[0].commandName, 'ListPoliciesCommand');
|
|
55
|
+
});
|
|
56
|
+
test('discovers policy successfully when it exists', async () => {
|
|
57
|
+
const expectedArn = 'arn:aws:iam::123456789012:policy/my-policy';
|
|
58
|
+
mockIamResponses['ListPoliciesCommand'] = {
|
|
59
|
+
Policies: [{ PolicyName: 'my-policy', Arn: expectedArn }]
|
|
60
|
+
};
|
|
61
|
+
const builder = new IAMPolicyBuilder('my-policy');
|
|
62
|
+
const discoveryResult = await builder.discoveryPromise;
|
|
63
|
+
assert.ok(discoveryResult);
|
|
64
|
+
assert.strictEqual(builder.resolvedArn, expectedArn);
|
|
65
|
+
const resolvedArn = await builder.out.arn.get();
|
|
66
|
+
assert.strictEqual(resolvedArn, expectedArn);
|
|
67
|
+
});
|
|
68
|
+
test('performs dry-run planning without making writes', async () => {
|
|
69
|
+
Config.set({
|
|
70
|
+
dryRun: true,
|
|
71
|
+
providers: { aws: { region: 'us-east-1' } }
|
|
72
|
+
});
|
|
73
|
+
mockIamResponses['ListPoliciesCommand'] = { Policies: [] };
|
|
74
|
+
const builder = new IAMPolicyBuilder('my-policy')
|
|
75
|
+
.document({
|
|
76
|
+
Version: '2012-10-17',
|
|
77
|
+
Statement: [{ Effect: 'Allow', Action: 's3:*', Resource: '*' }]
|
|
78
|
+
})
|
|
79
|
+
.description('Friendly desc');
|
|
80
|
+
const result = await builder.deploy();
|
|
81
|
+
assert.ok(result);
|
|
82
|
+
assert.strictEqual(result.arn, 'arn:aws:iam::000000000000:policy/DRYRUN-my-policy');
|
|
83
|
+
// Assert only discovery ListPolicies was sent, no writes
|
|
84
|
+
const writeCalls = iamCalls.filter(c => c.commandName !== 'ListPoliciesCommand');
|
|
85
|
+
assert.strictEqual(writeCalls.length, 0);
|
|
86
|
+
});
|
|
87
|
+
test('deploys new policy when missing', async () => {
|
|
88
|
+
mockIamResponses['ListPoliciesCommand'] = { Policies: [] };
|
|
89
|
+
mockIamResponses['CreatePolicyCommand'] = {
|
|
90
|
+
Policy: { Arn: 'arn:aws:iam::123456789012:policy/my-policy' }
|
|
91
|
+
};
|
|
92
|
+
const builder = new IAMPolicyBuilder('my-policy')
|
|
93
|
+
.document({
|
|
94
|
+
Version: '2012-10-17',
|
|
95
|
+
Statement: [{ Effect: 'Allow', Action: 's3:*', Resource: '*' }]
|
|
96
|
+
})
|
|
97
|
+
.description('My S3 Access Policy');
|
|
98
|
+
const result = await builder.deploy();
|
|
99
|
+
assert.ok(result);
|
|
100
|
+
assert.strictEqual(result.arn, 'arn:aws:iam::123456789012:policy/my-policy');
|
|
101
|
+
const createCall = iamCalls.find(c => c.commandName === 'CreatePolicyCommand');
|
|
102
|
+
assert.ok(createCall);
|
|
103
|
+
assert.strictEqual(createCall.input.PolicyName, 'my-policy');
|
|
104
|
+
assert.strictEqual(createCall.input.Description, 'My S3 Access Policy');
|
|
105
|
+
assert.deepStrictEqual(JSON.parse(createCall.input.PolicyDocument), {
|
|
106
|
+
Version: '2012-10-17',
|
|
107
|
+
Statement: [{ Effect: 'Allow', Action: 's3:*', Resource: '*' }]
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
test('updates policy creating new version and pruning oldest if versions >= 5', async () => {
|
|
111
|
+
const policyArn = 'arn:aws:iam::123456789012:policy/my-policy';
|
|
112
|
+
mockIamResponses['ListPoliciesCommand'] = {
|
|
113
|
+
Policies: [{ PolicyName: 'my-policy', Arn: policyArn }]
|
|
114
|
+
};
|
|
115
|
+
// Simulate 5 existing versions
|
|
116
|
+
mockIamResponses['ListPolicyVersionsCommand'] = {
|
|
117
|
+
Versions: [
|
|
118
|
+
{ VersionId: 'v1', IsDefaultVersion: false, CreateDate: new Date('2026-01-01') },
|
|
119
|
+
{ VersionId: 'v2', IsDefaultVersion: false, CreateDate: new Date('2026-01-02') },
|
|
120
|
+
{ VersionId: 'v3', IsDefaultVersion: false, CreateDate: new Date('2026-01-03') },
|
|
121
|
+
{ VersionId: 'v4', IsDefaultVersion: false, CreateDate: new Date('2026-01-04') },
|
|
122
|
+
{ VersionId: 'v5', IsDefaultVersion: true, CreateDate: new Date('2026-01-05') },
|
|
123
|
+
]
|
|
124
|
+
};
|
|
125
|
+
mockIamResponses['DeletePolicyVersionCommand'] = {};
|
|
126
|
+
mockIamResponses['CreatePolicyVersionCommand'] = {};
|
|
127
|
+
const builder = new IAMPolicyBuilder('my-policy')
|
|
128
|
+
.document({
|
|
129
|
+
Version: '2012-10-17',
|
|
130
|
+
Statement: [{ Effect: 'Allow', Action: 's3:GetObject', Resource: '*' }]
|
|
131
|
+
});
|
|
132
|
+
await builder.deploy();
|
|
133
|
+
// Assert oldest version v1 was deleted
|
|
134
|
+
const deleteCall = iamCalls.find(c => c.commandName === 'DeletePolicyVersionCommand');
|
|
135
|
+
assert.ok(deleteCall);
|
|
136
|
+
assert.strictEqual(deleteCall.input.PolicyArn, policyArn);
|
|
137
|
+
assert.strictEqual(deleteCall.input.VersionId, 'v1');
|
|
138
|
+
// Assert new version was created
|
|
139
|
+
const versionCall = iamCalls.find(c => c.commandName === 'CreatePolicyVersionCommand');
|
|
140
|
+
assert.ok(versionCall);
|
|
141
|
+
assert.strictEqual(versionCall.input.PolicyArn, policyArn);
|
|
142
|
+
assert.strictEqual(versionCall.input.SetAsDefault, true);
|
|
143
|
+
});
|
|
144
|
+
test('destroys custom managed policy and cleans up versions successfully', async () => {
|
|
145
|
+
const policyArn = 'arn:aws:iam::123456789012:policy/my-policy';
|
|
146
|
+
mockIamResponses['ListPoliciesCommand'] = {
|
|
147
|
+
Policies: [{ PolicyName: 'my-policy', Arn: policyArn }]
|
|
148
|
+
};
|
|
149
|
+
mockIamResponses['ListPolicyVersionsCommand'] = {
|
|
150
|
+
Versions: [
|
|
151
|
+
{ VersionId: 'v1', IsDefaultVersion: false },
|
|
152
|
+
{ VersionId: 'v2', IsDefaultVersion: true }
|
|
153
|
+
]
|
|
154
|
+
};
|
|
155
|
+
mockIamResponses['DeletePolicyVersionCommand'] = {};
|
|
156
|
+
mockIamResponses['DeletePolicyCommand'] = {};
|
|
157
|
+
const builder = new IAMPolicyBuilder('my-policy');
|
|
158
|
+
await builder.discoveryPromise;
|
|
159
|
+
const result = await builder.destroy();
|
|
160
|
+
assert.deepStrictEqual(result, { destroyed: 'my-policy' });
|
|
161
|
+
// Non-default version v1 must be deleted
|
|
162
|
+
const deleteVersionCall = iamCalls.find(c => c.commandName === 'DeletePolicyVersionCommand');
|
|
163
|
+
assert.ok(deleteVersionCall);
|
|
164
|
+
assert.strictEqual(deleteVersionCall.input.VersionId, 'v1');
|
|
165
|
+
// Main policy deleted
|
|
166
|
+
const deletePolicyCall = iamCalls.find(c => c.commandName === 'DeletePolicyCommand');
|
|
167
|
+
assert.ok(deletePolicyCall);
|
|
168
|
+
assert.strictEqual(deletePolicyCall.input.PolicyArn, policyArn);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
describe('IAMRoleBuilder Tests', () => {
|
|
172
|
+
test('gracefully handles discovery when role does not exist', async () => {
|
|
173
|
+
const noRoleError = new Error('NoSuchEntityException');
|
|
174
|
+
noRoleError.name = 'NoSuchEntityException';
|
|
175
|
+
mockIamResponses['GetRoleCommand'] = noRoleError;
|
|
176
|
+
const builder = new IAMRoleBuilder('my-role');
|
|
177
|
+
const discoveryResult = await builder.discoveryPromise;
|
|
178
|
+
assert.strictEqual(discoveryResult, null);
|
|
179
|
+
assert.strictEqual(iamCalls.length, 1);
|
|
180
|
+
assert.strictEqual(iamCalls[0].commandName, 'GetRoleCommand');
|
|
181
|
+
});
|
|
182
|
+
test('discovers role successfully when it exists', async () => {
|
|
183
|
+
const expectedArn = 'arn:aws:iam::123456789012:role/my-role';
|
|
184
|
+
mockIamResponses['GetRoleCommand'] = {
|
|
185
|
+
Role: { RoleName: 'my-role', Arn: expectedArn }
|
|
186
|
+
};
|
|
187
|
+
const builder = new IAMRoleBuilder('my-role');
|
|
188
|
+
const discoveryResult = await builder.discoveryPromise;
|
|
189
|
+
assert.ok(discoveryResult);
|
|
190
|
+
assert.strictEqual(builder.resolvedArn, expectedArn);
|
|
191
|
+
const resolvedArn = await builder.out.arn.get();
|
|
192
|
+
assert.strictEqual(resolvedArn, expectedArn);
|
|
193
|
+
const resolvedName = await builder.out.name.get();
|
|
194
|
+
assert.strictEqual(resolvedName, 'my-role');
|
|
195
|
+
});
|
|
196
|
+
test('performs dry-run planning without making writes', async () => {
|
|
197
|
+
Config.set({
|
|
198
|
+
dryRun: true,
|
|
199
|
+
providers: { aws: { region: 'us-east-1' } }
|
|
200
|
+
});
|
|
201
|
+
const noRoleError = new Error('NoSuchEntityException');
|
|
202
|
+
noRoleError.name = 'NoSuchEntityException';
|
|
203
|
+
mockIamResponses['GetRoleCommand'] = noRoleError;
|
|
204
|
+
const builder = new IAMRoleBuilder('my-role')
|
|
205
|
+
.assumeRolePolicy({
|
|
206
|
+
Statement: [{ Effect: 'Allow', Principal: { Service: 'ec2.amazonaws.com' }, Action: 'sts:AssumeRole' }]
|
|
207
|
+
});
|
|
208
|
+
const result = await builder.deploy();
|
|
209
|
+
assert.ok(result);
|
|
210
|
+
assert.strictEqual(result.arn, 'arn:aws:iam::000000000000:role/DRYRUN-my-role');
|
|
211
|
+
// Only discovery GetRole was sent
|
|
212
|
+
const writeCalls = iamCalls.filter(c => c.commandName !== 'GetRoleCommand');
|
|
213
|
+
assert.strictEqual(writeCalls.length, 0);
|
|
214
|
+
});
|
|
215
|
+
test('deploys new role with assume-role trust policy, managed attachments, and inline statements', async () => {
|
|
216
|
+
const noRoleError = new Error('NoSuchEntityException');
|
|
217
|
+
noRoleError.name = 'NoSuchEntityException';
|
|
218
|
+
mockIamResponses['GetRoleCommand'] = noRoleError;
|
|
219
|
+
const expectedArn = 'arn:aws:iam::123456789012:role/my-role';
|
|
220
|
+
mockIamResponses['CreateRoleCommand'] = { Role: { Arn: expectedArn } };
|
|
221
|
+
mockIamResponses['ListAttachedRolePoliciesCommand'] = { AttachedPolicies: [] };
|
|
222
|
+
mockIamResponses['AttachRolePolicyCommand'] = {};
|
|
223
|
+
mockIamResponses['ListRolePoliciesCommand'] = { PolicyNames: [] };
|
|
224
|
+
mockIamResponses['PutRolePolicyCommand'] = {};
|
|
225
|
+
const builder = new IAMRoleBuilder('my-role')
|
|
226
|
+
.assumeRolePolicy({
|
|
227
|
+
Statement: [{ Effect: 'Allow', Principal: { Service: 'lambda.amazonaws.com' }, Action: 'sts:AssumeRole' }]
|
|
228
|
+
})
|
|
229
|
+
.attach('arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess')
|
|
230
|
+
.inlinePolicy('my-inline', {
|
|
231
|
+
Statement: [{ Effect: 'Allow', Action: 'sqs:*', Resource: '*' }]
|
|
232
|
+
});
|
|
233
|
+
const result = await builder.deploy();
|
|
234
|
+
assert.ok(result);
|
|
235
|
+
assert.strictEqual(result.arn, expectedArn);
|
|
236
|
+
// Verify CreateRole arguments
|
|
237
|
+
const createCall = iamCalls.find(c => c.commandName === 'CreateRoleCommand');
|
|
238
|
+
assert.ok(createCall);
|
|
239
|
+
assert.strictEqual(createCall.input.RoleName, 'my-role');
|
|
240
|
+
assert.deepStrictEqual(JSON.parse(createCall.input.AssumeRolePolicyDocument), {
|
|
241
|
+
Statement: [{ Effect: 'Allow', Principal: { Service: 'lambda.amazonaws.com' }, Action: 'sts:AssumeRole' }]
|
|
242
|
+
});
|
|
243
|
+
// Verify Managed Policy attachment
|
|
244
|
+
const attachCall = iamCalls.find(c => c.commandName === 'AttachRolePolicyCommand');
|
|
245
|
+
assert.ok(attachCall);
|
|
246
|
+
assert.strictEqual(attachCall.input.RoleName, 'my-role');
|
|
247
|
+
assert.strictEqual(attachCall.input.PolicyArn, 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess');
|
|
248
|
+
// Verify Inline Policy creation
|
|
249
|
+
const inlineCall = iamCalls.find(c => c.commandName === 'PutRolePolicyCommand');
|
|
250
|
+
assert.ok(inlineCall);
|
|
251
|
+
assert.strictEqual(inlineCall.input.RoleName, 'my-role');
|
|
252
|
+
assert.strictEqual(inlineCall.input.PolicyName, 'my-inline');
|
|
253
|
+
assert.deepStrictEqual(JSON.parse(inlineCall.input.PolicyDocument), {
|
|
254
|
+
Statement: [{ Effect: 'Allow', Action: 'sqs:*', Resource: '*' }]
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
test('updates existing role, syncing managed policy attachments and inline policies', async () => {
|
|
258
|
+
mockIamResponses['GetRoleCommand'] = {
|
|
259
|
+
Role: { RoleName: 'my-role', Arn: 'arn:aws:iam::123456789012:role/my-role' }
|
|
260
|
+
};
|
|
261
|
+
mockIamResponses['UpdateAssumeRolePolicyCommand'] = {};
|
|
262
|
+
mockIamResponses['UpdateRoleCommand'] = {};
|
|
263
|
+
// Current managed policies: AmazonS3ReadOnlyAccess, AmazonDynamoDBFullAccess (stale)
|
|
264
|
+
mockIamResponses['ListAttachedRolePoliciesCommand'] = {
|
|
265
|
+
AttachedPolicies: [
|
|
266
|
+
{ PolicyName: 'AmazonS3ReadOnlyAccess', PolicyArn: 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess' },
|
|
267
|
+
{ PolicyName: 'AmazonDynamoDBFullAccess', PolicyArn: 'arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess' }
|
|
268
|
+
]
|
|
269
|
+
};
|
|
270
|
+
mockIamResponses['AttachRolePolicyCommand'] = {};
|
|
271
|
+
mockIamResponses['DetachRolePolicyCommand'] = {};
|
|
272
|
+
// Current inline policies: stale-inline, matching-inline
|
|
273
|
+
mockIamResponses['ListRolePoliciesCommand'] = {
|
|
274
|
+
PolicyNames: ['stale-inline', 'matching-inline']
|
|
275
|
+
};
|
|
276
|
+
mockIamResponses['PutRolePolicyCommand'] = {};
|
|
277
|
+
mockIamResponses['DeleteRolePolicyCommand'] = {};
|
|
278
|
+
// Configured builder wants: AmazonS3ReadOnlyAccess, AmazonSQSFullAccess (new), and 'matching-inline'
|
|
279
|
+
const builder = new IAMRoleBuilder('my-role')
|
|
280
|
+
.attach('arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess')
|
|
281
|
+
.attach('arn:aws:iam::aws:policy/AmazonSQSFullAccess')
|
|
282
|
+
.inlinePolicy('matching-inline', {
|
|
283
|
+
Statement: [{ Effect: 'Allow', Action: 'sqs:*', Resource: '*' }]
|
|
284
|
+
});
|
|
285
|
+
await builder.deploy();
|
|
286
|
+
// Assert Detach is called on stale AmazonDynamoDBFullAccess
|
|
287
|
+
const detachCall = iamCalls.find(c => c.commandName === 'DetachRolePolicyCommand');
|
|
288
|
+
assert.ok(detachCall);
|
|
289
|
+
assert.strictEqual(detachCall.input.PolicyArn, 'arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess');
|
|
290
|
+
// Assert Attach is called on new AmazonSQSFullAccess
|
|
291
|
+
const attachCall = iamCalls.find(c => c.commandName === 'AttachRolePolicyCommand');
|
|
292
|
+
assert.ok(attachCall);
|
|
293
|
+
assert.strictEqual(attachCall.input.PolicyArn, 'arn:aws:iam::aws:policy/AmazonSQSFullAccess');
|
|
294
|
+
// Assert Delete is called on stale-inline policy
|
|
295
|
+
const deleteInlineCall = iamCalls.find(c => c.commandName === 'DeleteRolePolicyCommand');
|
|
296
|
+
assert.ok(deleteInlineCall);
|
|
297
|
+
assert.strictEqual(deleteInlineCall.input.PolicyName, 'stale-inline');
|
|
298
|
+
// Assert Put is called on matching-inline policy
|
|
299
|
+
const putInlineCall = iamCalls.find(c => c.commandName === 'PutRolePolicyCommand');
|
|
300
|
+
assert.ok(putInlineCall);
|
|
301
|
+
assert.strictEqual(putInlineCall.input.PolicyName, 'matching-inline');
|
|
302
|
+
});
|
|
303
|
+
test('destroys existing role cleaning up inline policies and managed attachments', async () => {
|
|
304
|
+
mockIamResponses['GetRoleCommand'] = {
|
|
305
|
+
Role: { RoleName: 'my-role', Arn: 'arn:aws:iam::123456789012:role/my-role' }
|
|
306
|
+
};
|
|
307
|
+
mockIamResponses['ListRolePoliciesCommand'] = { PolicyNames: ['inline-one'] };
|
|
308
|
+
mockIamResponses['DeleteRolePolicyCommand'] = {};
|
|
309
|
+
mockIamResponses['ListAttachedRolePoliciesCommand'] = {
|
|
310
|
+
AttachedPolicies: [{ PolicyName: 'S3Access', PolicyArn: 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess' }]
|
|
311
|
+
};
|
|
312
|
+
mockIamResponses['DetachRolePolicyCommand'] = {};
|
|
313
|
+
mockIamResponses['DeleteRoleCommand'] = {};
|
|
314
|
+
const builder = new IAMRoleBuilder('my-role');
|
|
315
|
+
await builder.discoveryPromise;
|
|
316
|
+
const result = await builder.destroy();
|
|
317
|
+
assert.deepStrictEqual(result, { destroyed: 'my-role' });
|
|
318
|
+
// Inline policy deleted
|
|
319
|
+
const deleteInline = iamCalls.find(c => c.commandName === 'DeleteRolePolicyCommand');
|
|
320
|
+
assert.ok(deleteInline);
|
|
321
|
+
assert.strictEqual(deleteInline.input.PolicyName, 'inline-one');
|
|
322
|
+
// Managed policy detached
|
|
323
|
+
const detachCall = iamCalls.find(c => c.commandName === 'DetachRolePolicyCommand');
|
|
324
|
+
assert.ok(detachCall);
|
|
325
|
+
assert.strictEqual(detachCall.input.PolicyArn, 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess');
|
|
326
|
+
// Role deleted
|
|
327
|
+
const deleteRole = iamCalls.find(c => c.commandName === 'DeleteRoleCommand');
|
|
328
|
+
assert.ok(deleteRole);
|
|
329
|
+
assert.strictEqual(deleteRole.input.RoleName, 'my-role');
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
describe('Lambda Integration', () => {
|
|
333
|
+
test('LambdaBuilder accepts IAMRoleBuilder and resolves its eager output ARN successfully', async () => {
|
|
334
|
+
// 1. Mock Role discovery to succeed
|
|
335
|
+
const expectedRoleArn = 'arn:aws:iam::123456789012:role/my-custom-role';
|
|
336
|
+
mockIamResponses['GetRoleCommand'] = {
|
|
337
|
+
Role: { RoleName: 'my-custom-role', Arn: expectedRoleArn }
|
|
338
|
+
};
|
|
339
|
+
const roleBuilder = new IAMRoleBuilder('my-custom-role');
|
|
340
|
+
// 2. Mock Lambda discovery to report not found (forces deploy ensureRole)
|
|
341
|
+
let lambdaCalls = [];
|
|
342
|
+
const originalLambdaSend = LambdaClient.prototype.send;
|
|
343
|
+
LambdaClient.prototype.send = async function (command) {
|
|
344
|
+
const commandName = command.constructor.name;
|
|
345
|
+
lambdaCalls.push({ commandName, input: command.input });
|
|
346
|
+
if (commandName === 'GetFunctionCommand') {
|
|
347
|
+
const notFoundError = new Error('Function not found');
|
|
348
|
+
notFoundError.name = 'ResourceNotFoundException';
|
|
349
|
+
throw notFoundError;
|
|
350
|
+
}
|
|
351
|
+
return { FunctionArn: 'arn:aws:lambda:us-east-1:12345:function:my-fn' };
|
|
352
|
+
};
|
|
353
|
+
// Mock fast-forward setTimeout
|
|
354
|
+
mock.method(global, 'setTimeout', (fn) => fn());
|
|
355
|
+
const lambdaBuilder = new LambdaBuilder('my-fn');
|
|
356
|
+
lambdaBuilder
|
|
357
|
+
.code('my-code.zip')
|
|
358
|
+
.role(roleBuilder); // custom role builder integration!
|
|
359
|
+
await lambdaBuilder.deploy();
|
|
360
|
+
// Assert Lambda was created using the custom role builder's ARN eagerly
|
|
361
|
+
const createFnCall = lambdaCalls.find(c => c.commandName === 'CreateFunctionCommand');
|
|
362
|
+
assert.ok(createFnCall);
|
|
363
|
+
assert.strictEqual(createFnCall.input.Role, expectedRoleArn);
|
|
364
|
+
LambdaClient.prototype.send = originalLambdaSend;
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
});
|