@fishawack/lab-env 5.7.0-beta.1 → 5.7.0-beta.3

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.
Files changed (37) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/_Test/provision.js +4 -9
  3. package/_Test/prune.js +8 -5
  4. package/bitbucket-pipelines.yml +2 -2
  5. package/cli.js +1 -0
  6. package/commands/create/cmds/dekey.js +13 -9
  7. package/commands/create/cmds/deprovision.js +36 -0
  8. package/commands/create/cmds/key.js +60 -41
  9. package/commands/create/cmds/provision.js +573 -69
  10. package/commands/create/cmds/rekey.js +218 -0
  11. package/commands/create/cmds/sync-secrets.js +2 -1
  12. package/commands/create/libs/output-credentials.js +59 -0
  13. package/commands/create/libs/parallel-runner.js +204 -0
  14. package/commands/create/libs/resolve-operator.js +59 -0
  15. package/commands/create/libs/utilities.js +73 -8
  16. package/commands/create/libs/vars.js +120 -136
  17. package/commands/create/services/aws/acm.js +61 -0
  18. package/commands/create/services/aws/cloudfront.js +51 -45
  19. package/commands/create/services/aws/ec2.js +675 -48
  20. package/commands/create/services/aws/elasticache.js +159 -0
  21. package/commands/create/services/aws/elasticbeanstalk.js +154 -57
  22. package/commands/create/services/aws/iam.js +402 -82
  23. package/commands/create/services/aws/index.js +1398 -260
  24. package/commands/create/services/aws/opensearch.js +155 -0
  25. package/commands/create/services/aws/rds.js +57 -31
  26. package/commands/create/services/aws/s3.js +52 -31
  27. package/commands/create/services/aws/secretsmanager.js +21 -12
  28. package/commands/create/services/aws/ses.js +195 -0
  29. package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/cron.config +45 -0
  30. package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/queue-worker.config +29 -0
  31. package/commands/create/templates/elasticbeanstalk/.ebextensions/laravel/software.config +12 -0
  32. package/commands/create/templates/elasticbeanstalk/.ebextensions/misc/setvars.config +2 -16
  33. package/commands/create/templates/elasticbeanstalk/.platform/confighooks/postdeploy/rewrite-flush.sh +1 -1
  34. package/commands/create/templates/elasticbeanstalk/.platform/confighooks/postdeploy/setvars.sh +19 -0
  35. package/commands/create/templates/elasticbeanstalk/.platform/hooks/postdeploy/restart_queue.sh +4 -0
  36. package/commands/create/templates/elasticbeanstalk/.platform/hooks/prebuild/setvars.sh +19 -0
  37. package/package.json +7 -2
@@ -9,25 +9,35 @@ const {
9
9
  CreateAccessKeyCommand,
10
10
  DeleteAccessKeyCommand,
11
11
  ListAccessKeysCommand,
12
+ AddUserToGroupCommand,
12
13
  GetRoleCommand,
13
14
  CreateRoleCommand,
15
+ DeleteRoleCommand,
14
16
  AttachRolePolicyCommand,
17
+ DetachRolePolicyCommand,
18
+ ListAttachedRolePoliciesCommand,
15
19
  CreateInstanceProfileCommand,
20
+ DeleteInstanceProfileCommand,
16
21
  AddRoleToInstanceProfileCommand,
22
+ RemoveRoleFromInstanceProfileCommand,
17
23
  PutRolePolicyCommand,
24
+ DeleteRolePolicyCommand,
25
+ ListRolePoliciesCommand,
26
+ CreateServiceLinkedRoleCommand,
18
27
  } = require("@aws-sdk/client-iam");
19
28
  const { Spinner } = require("../../libs/utilities");
29
+ const { awsClientConfig } = require("../../libs/vars");
20
30
 
21
- module.exports.createIAMUser = async (UserName, tags = []) => {
22
- const client = new IAMClient({});
31
+ const getClient = () => new IAMClient(awsClientConfig);
23
32
 
33
+ module.exports.createIAMUser = async (UserName, tags = []) => {
24
34
  let res;
25
35
 
26
36
  try {
27
37
  res = await Spinner.prototype.simple(
28
38
  `Creating IAM user ${UserName}`,
29
39
  () => {
30
- return client.send(
40
+ return getClient().send(
31
41
  new CreateUserCommand({
32
42
  UserName,
33
43
  Tags: [
@@ -41,7 +51,7 @@ module.exports.createIAMUser = async (UserName, tags = []) => {
41
51
  res = await Spinner.prototype.simple(
42
52
  `Retrieving the already existing IAM user ${UserName}`,
43
53
  () => {
44
- return client.send(new GetUserCommand({ UserName }));
54
+ return getClient().send(new GetUserCommand({ UserName }));
45
55
  },
46
56
  );
47
57
  }
@@ -59,16 +69,25 @@ module.exports.createFWIAMUser = async (UserName, permissions) => {
59
69
  return res;
60
70
  };
61
71
 
62
- module.exports.removeIAMUser = async (UserName) => {
63
- const client = new IAMClient({});
72
+ module.exports.addUserToGroup = async (UserName, GroupName, iamClient) => {
73
+ const c = iamClient || getClient();
74
+
75
+ await Spinner.prototype.simple(
76
+ `Adding IAM user ${UserName} to group ${GroupName}`,
77
+ () => {
78
+ return c.send(new AddUserToGroupCommand({ UserName, GroupName }));
79
+ },
80
+ );
81
+ };
64
82
 
83
+ module.exports.removeIAMUser = async (UserName) => {
65
84
  let res;
66
85
 
67
86
  try {
68
87
  await Spinner.prototype.simple(
69
88
  `Checking if IAM user ${UserName} exists`,
70
89
  () => {
71
- return client.send(new GetUserCommand({ UserName }));
90
+ return getClient().send(new GetUserCommand({ UserName }));
72
91
  },
73
92
  );
74
93
 
@@ -79,7 +98,7 @@ module.exports.removeIAMUser = async (UserName) => {
79
98
  res = await Spinner.prototype.simple(
80
99
  `Removing IAM user ${UserName}`,
81
100
  () => {
82
- return client.send(new DeleteUserCommand({ UserName }));
101
+ return getClient().send(new DeleteUserCommand({ UserName }));
83
102
  },
84
103
  );
85
104
  } catch {
@@ -90,12 +109,10 @@ module.exports.removeIAMUser = async (UserName) => {
90
109
  };
91
110
 
92
111
  module.exports.attachIAMPolicy = async (UserName, policy) => {
93
- const client = new IAMClient({});
94
-
95
112
  let res = await Spinner.prototype.simple(
96
113
  `Attaching IAM policy ${policy}`,
97
114
  () => {
98
- return client.send(
115
+ return getClient().send(
99
116
  new AttachUserPolicyCommand({ UserName, PolicyArn: policy }),
100
117
  );
101
118
  },
@@ -107,68 +124,83 @@ module.exports.attachIAMPolicy = async (UserName, policy) => {
107
124
  module.exports.syncFWIAMPolicies = async (
108
125
  UserName,
109
126
  permissions = ["auto-content", "deploy-static"],
127
+ preserve = [],
110
128
  ) => {
111
- await module.exports.removeAllIAMPolicies(UserName);
129
+ let res = await module.exports.listIAMPolicies(UserName);
130
+
131
+ // Detach existing policies in parallel
132
+ const toDetach = res.AttachedPolicies.filter(
133
+ (p) => !preserve.includes(p.PolicyArn),
134
+ );
135
+ if (toDetach.length) {
136
+ await Spinner.prototype.simple(
137
+ `Detaching ${toDetach.length} IAM policies`,
138
+ () =>
139
+ Promise.all(
140
+ toDetach.map((p) =>
141
+ getClient().send(
142
+ new DetachUserPolicyCommand({
143
+ UserName,
144
+ PolicyArn: p.PolicyArn,
145
+ }),
146
+ ),
147
+ ),
148
+ ),
149
+ );
150
+ }
151
+
152
+ // Collect policies to attach
153
+ const policies = [];
112
154
 
113
155
  if (
114
156
  permissions.includes("auto-content") ||
115
157
  permissions.includes("deploy-static")
116
158
  ) {
117
- await module.exports.attachIAMPolicy(
118
- UserName,
119
- "arn:aws:iam::aws:policy/AmazonS3FullAccess",
120
- );
159
+ policies.push("arn:aws:iam::aws:policy/AmazonS3FullAccess");
121
160
  }
122
161
 
123
162
  if (permissions.includes("deploy-static")) {
124
- await module.exports.attachIAMPolicy(
125
- UserName,
126
- "arn:aws:iam::aws:policy/CloudFrontFullAccess",
127
- );
163
+ policies.push("arn:aws:iam::aws:policy/CloudFrontFullAccess");
128
164
  }
129
165
 
130
166
  if (permissions.includes("deploy-fullstack")) {
131
- await module.exports.attachIAMPolicy(
132
- UserName,
167
+ policies.push(
133
168
  "arn:aws:iam::aws:policy/AdministratorAccess-AWSElasticBeanstalk",
134
- );
135
-
136
- await module.exports.attachIAMPolicy(
137
- UserName,
138
169
  "arn:aws:iam::aws:policy/AmazonOpenSearchServiceFullAccess",
139
- );
140
-
141
- await module.exports.attachIAMPolicy(
142
- UserName,
143
170
  "arn:aws:iam::aws:policy/AmazonRDSFullAccess",
144
- );
145
-
146
- await module.exports.attachIAMPolicy(
147
- UserName,
148
171
  "arn:aws:iam::aws:policy/AmazonElastiCacheFullAccess",
149
- );
150
-
151
- await module.exports.attachIAMPolicy(
152
- UserName,
153
172
  "arn:aws:iam::aws:policy/SecretsManagerReadWrite",
154
173
  );
155
174
  }
156
175
 
157
176
  if (permissions.includes("manage-users")) {
158
- await module.exports.attachIAMPolicy(
159
- UserName,
160
- "arn:aws:iam::aws:policy/IAMFullAccess",
177
+ policies.push("arn:aws:iam::aws:policy/IAMFullAccess");
178
+ }
179
+
180
+ // Attach all policies in parallel
181
+ if (policies.length) {
182
+ await Spinner.prototype.simple(
183
+ `Attaching ${policies.length} IAM policies`,
184
+ () =>
185
+ Promise.all(
186
+ policies.map((p) =>
187
+ getClient().send(
188
+ new AttachUserPolicyCommand({
189
+ UserName,
190
+ PolicyArn: p,
191
+ }),
192
+ ),
193
+ ),
194
+ ),
161
195
  );
162
196
  }
163
197
  };
164
198
 
165
199
  module.exports.removeIAMPolicy = async (UserName, policy) => {
166
- const client = new IAMClient({});
167
-
168
200
  let res = await Spinner.prototype.simple(
169
201
  `Detaching IAM policy ${policy}`,
170
202
  () => {
171
- return client.send(
203
+ return getClient().send(
172
204
  new DetachUserPolicyCommand({ UserName, PolicyArn: policy }),
173
205
  );
174
206
  },
@@ -178,10 +210,10 @@ module.exports.removeIAMPolicy = async (UserName, policy) => {
178
210
  };
179
211
 
180
212
  module.exports.listIAMPolicies = async (UserName) => {
181
- const client = new IAMClient({});
182
-
183
213
  let res = await Spinner.prototype.simple(`Listing IAM policies`, () => {
184
- return client.send(new ListAttachedUserPoliciesCommand({ UserName }));
214
+ return getClient().send(
215
+ new ListAttachedUserPoliciesCommand({ UserName }),
216
+ );
185
217
  });
186
218
 
187
219
  return res;
@@ -190,10 +222,20 @@ module.exports.listIAMPolicies = async (UserName) => {
190
222
  module.exports.removeAllIAMPolicies = async (UserName) => {
191
223
  let res = await module.exports.listIAMPolicies(UserName);
192
224
 
193
- for (let i = 0; i < res.AttachedPolicies.length; i++) {
194
- await module.exports.removeIAMPolicy(
195
- UserName,
196
- res.AttachedPolicies[i].PolicyArn,
225
+ if (res.AttachedPolicies.length) {
226
+ await Spinner.prototype.simple(
227
+ `Detaching ${res.AttachedPolicies.length} IAM policies`,
228
+ () =>
229
+ Promise.all(
230
+ res.AttachedPolicies.map((p) =>
231
+ getClient().send(
232
+ new DetachUserPolicyCommand({
233
+ UserName,
234
+ PolicyArn: p.PolicyArn,
235
+ }),
236
+ ),
237
+ ),
238
+ ),
197
239
  );
198
240
  }
199
241
 
@@ -201,22 +243,18 @@ module.exports.removeAllIAMPolicies = async (UserName) => {
201
243
  };
202
244
 
203
245
  module.exports.createAccessKey = async (UserName) => {
204
- const client = new IAMClient({});
205
-
206
246
  let res = await Spinner.prototype.simple(`Creating access key`, () => {
207
- return client.send(new CreateAccessKeyCommand({ UserName }));
247
+ return getClient().send(new CreateAccessKeyCommand({ UserName }));
208
248
  });
209
249
 
210
250
  return res;
211
251
  };
212
252
 
213
253
  module.exports.removeAccessKey = async (UserName, AccessKeyId) => {
214
- const client = new IAMClient({});
215
-
216
254
  let res = await Spinner.prototype.simple(
217
255
  `Removing access key ${AccessKeyId}`,
218
256
  () => {
219
- return client.send(
257
+ return getClient().send(
220
258
  new DeleteAccessKeyCommand({ AccessKeyId, UserName }),
221
259
  );
222
260
  },
@@ -226,10 +264,8 @@ module.exports.removeAccessKey = async (UserName, AccessKeyId) => {
226
264
  };
227
265
 
228
266
  module.exports.listAccessKeys = async (UserName) => {
229
- const client = new IAMClient({});
230
-
231
267
  let res = await Spinner.prototype.simple(`Listing access keys`, () => {
232
- return client.send(new ListAccessKeysCommand({ UserName }));
268
+ return getClient().send(new ListAccessKeysCommand({ UserName }));
233
269
  });
234
270
 
235
271
  return res;
@@ -238,10 +274,20 @@ module.exports.listAccessKeys = async (UserName) => {
238
274
  module.exports.removeAllAccessKeys = async (UserName) => {
239
275
  let res = await module.exports.listAccessKeys(UserName);
240
276
 
241
- for (let i = 0; i < res.AccessKeyMetadata.length; i++) {
242
- await module.exports.removeAccessKey(
243
- UserName,
244
- res.AccessKeyMetadata[i].AccessKeyId,
277
+ if (res.AccessKeyMetadata.length) {
278
+ await Spinner.prototype.simple(
279
+ `Removing ${res.AccessKeyMetadata.length} access keys`,
280
+ () =>
281
+ Promise.all(
282
+ res.AccessKeyMetadata.map((k) =>
283
+ getClient().send(
284
+ new DeleteAccessKeyCommand({
285
+ UserName,
286
+ AccessKeyId: k.AccessKeyId,
287
+ }),
288
+ ),
289
+ ),
290
+ ),
245
291
  );
246
292
  }
247
293
 
@@ -258,13 +304,52 @@ module.exports.createAccessKeySafe = async (UserName) => {
258
304
  return res;
259
305
  };
260
306
 
261
- module.exports.getRole = async (RoleName) => {
262
- const client = new IAMClient({});
307
+ module.exports.rotateAccessKey = async (UserName) => {
308
+ let res = await module.exports.listAccessKeys(UserName);
309
+ let keys = res.AccessKeyMetadata;
310
+
311
+ if (keys.length === 2) {
312
+ console.log(
313
+ `\n⚠ ${UserName} has 2 existing keys (likely from a failed previous rotation). Removing the older key to make room.\n`,
314
+ );
315
+
316
+ keys.sort((a, b) => new Date(a.CreateDate) - new Date(b.CreateDate));
317
+
318
+ await module.exports.removeAccessKey(UserName, keys[0].AccessKeyId);
319
+
320
+ keys = [keys[1]];
321
+ }
322
+
323
+ let newKey = await module.exports.createAccessKey(UserName);
324
+
325
+ if (keys.length === 1) {
326
+ await module.exports.removeAccessKey(UserName, keys[0].AccessKeyId);
327
+ }
328
+
329
+ return newKey;
330
+ };
331
+
332
+ module.exports.rotateFWIAMUser = async (UserName, permissions) => {
333
+ await module.exports.createIAMUser(UserName);
334
+
335
+ let preserve = [];
336
+
337
+ if (permissions && permissions.includes("manage-users")) {
338
+ preserve.push("arn:aws:iam::aws:policy/IAMFullAccess");
339
+ }
340
+
341
+ await module.exports.syncFWIAMPolicies(UserName, permissions, preserve);
342
+
343
+ let res = await module.exports.rotateAccessKey(UserName);
263
344
 
345
+ return res;
346
+ };
347
+
348
+ module.exports.getRole = async (RoleName) => {
264
349
  let res = await Spinner.prototype.simple(
265
350
  `Retrieving the role ${RoleName}`,
266
351
  () => {
267
- return client.send(new GetRoleCommand({ RoleName }));
352
+ return getClient().send(new GetRoleCommand({ RoleName }));
268
353
  },
269
354
  );
270
355
 
@@ -272,12 +357,10 @@ module.exports.getRole = async (RoleName) => {
272
357
  };
273
358
 
274
359
  module.exports.createRole = async (RoleName, AssumeRolePolicyDocument) => {
275
- const client = new IAMClient({});
276
-
277
360
  let res = await Spinner.prototype.simple(
278
361
  `Creating the role ${RoleName}`,
279
362
  () => {
280
- return client.send(
363
+ return getClient().send(
281
364
  new CreateRoleCommand({ RoleName, AssumeRolePolicyDocument }),
282
365
  );
283
366
  },
@@ -287,12 +370,10 @@ module.exports.createRole = async (RoleName, AssumeRolePolicyDocument) => {
287
370
  };
288
371
 
289
372
  module.exports.createInstanceProfile = async (InstanceProfileName) => {
290
- const client = new IAMClient({});
291
-
292
373
  let res = await Spinner.prototype.simple(
293
374
  `Creating the instance profile ${InstanceProfileName}`,
294
375
  () => {
295
- return client.send(
376
+ return getClient().send(
296
377
  new CreateInstanceProfileCommand({ InstanceProfileName }),
297
378
  );
298
379
  },
@@ -305,12 +386,10 @@ module.exports.attachRoleToInstanceProfile = async (
305
386
  RoleName,
306
387
  InstanceProfileName,
307
388
  ) => {
308
- const client = new IAMClient({});
309
-
310
389
  let res = await Spinner.prototype.simple(
311
390
  `Attaching role ${RoleName} to the instance profile ${InstanceProfileName}`,
312
391
  () => {
313
- return client.send(
392
+ return getClient().send(
314
393
  new AddRoleToInstanceProfileCommand({
315
394
  RoleName,
316
395
  InstanceProfileName,
@@ -323,12 +402,10 @@ module.exports.attachRoleToInstanceProfile = async (
323
402
  };
324
403
 
325
404
  module.exports.attachRolePolicy = async (RoleName, PolicyArn) => {
326
- const client = new IAMClient({});
327
-
328
405
  let res = await Spinner.prototype.simple(
329
406
  `Attaching Role policy ${PolicyArn}`,
330
407
  () => {
331
- return client.send(
408
+ return getClient().send(
332
409
  new AttachRolePolicyCommand({ RoleName, PolicyArn }),
333
410
  );
334
411
  },
@@ -342,12 +419,10 @@ module.exports.attachInlineRolePolicy = async (
342
419
  PolicyName,
343
420
  PolicyDocument,
344
421
  ) => {
345
- const client = new IAMClient({});
346
-
347
422
  let res = await Spinner.prototype.simple(
348
423
  `Attaching Inline Role policy ${PolicyName}`,
349
424
  () => {
350
- return client.send(
425
+ return getClient().send(
351
426
  new PutRolePolicyCommand({
352
427
  RoleName,
353
428
  PolicyName,
@@ -402,6 +477,10 @@ module.exports.ensureEBInstanceProfileExists = async () => {
402
477
  role,
403
478
  "arn:aws:iam::aws:policy/AWSSecretsManagerClientReadOnlyAccess",
404
479
  );
480
+ await module.exports.attachRolePolicy(
481
+ role,
482
+ "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
483
+ );
405
484
  await module.exports.attachInlineRolePolicy(
406
485
  role,
407
486
  "lab-env-elasticbeanstalk-describe-env",
@@ -504,3 +583,244 @@ module.exports.ensureEBManagedUpdateProfileExists = async () => {
504
583
  }),
505
584
  );
506
585
  };
586
+
587
+ module.exports.ensureEnvironmentInstanceProfile = async (
588
+ ebSlug,
589
+ s3BucketName,
590
+ opensearchDomainArn,
591
+ ) => {
592
+ const role = `${ebSlug}-role`;
593
+
594
+ try {
595
+ await module.exports.getRole(role);
596
+ } catch {
597
+ await module.exports.createRole(
598
+ role,
599
+ JSON.stringify({
600
+ Version: "2012-10-17",
601
+ Statement: [
602
+ {
603
+ Effect: "Allow",
604
+ Action: ["sts:AssumeRole"],
605
+ Principal: {
606
+ Service: ["ec2.amazonaws.com"],
607
+ },
608
+ },
609
+ ],
610
+ }),
611
+ );
612
+
613
+ await module.exports.createInstanceProfile(role);
614
+ await module.exports.attachRoleToInstanceProfile(role, role);
615
+ }
616
+
617
+ await module.exports.attachRolePolicy(
618
+ role,
619
+ "arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier",
620
+ );
621
+ await module.exports.attachRolePolicy(
622
+ role,
623
+ "arn:aws:iam::aws:policy/AWSElasticBeanstalkWorkerTier",
624
+ );
625
+ await module.exports.attachRolePolicy(
626
+ role,
627
+ "arn:aws:iam::aws:policy/AWSElasticBeanstalkMulticontainerDocker",
628
+ );
629
+ await module.exports.attachRolePolicy(
630
+ role,
631
+ "arn:aws:iam::aws:policy/AWSSecretsManagerClientReadOnlyAccess",
632
+ );
633
+ await module.exports.attachRolePolicy(
634
+ role,
635
+ "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
636
+ );
637
+ await module.exports.attachInlineRolePolicy(
638
+ role,
639
+ "lab-env-elasticbeanstalk-describe-env",
640
+ JSON.stringify({
641
+ Version: "2012-10-17",
642
+ Statement: [
643
+ {
644
+ Sid: "VisualEditor0",
645
+ Effect: "Allow",
646
+ Action: [
647
+ "ec2:DescribeTags",
648
+ "autoscaling:DescribeAutoScalingGroups",
649
+ "elasticbeanstalk:DescribeEvents",
650
+ ],
651
+ Resource: "*",
652
+ },
653
+ ],
654
+ }),
655
+ );
656
+
657
+ if (s3BucketName) {
658
+ await module.exports.attachInlineRolePolicy(
659
+ role,
660
+ "lab-env-s3-bucket-access",
661
+ JSON.stringify({
662
+ Version: "2012-10-17",
663
+ Statement: [
664
+ {
665
+ Sid: "S3BucketAccess",
666
+ Effect: "Allow",
667
+ Action: [
668
+ "s3:GetObject",
669
+ "s3:PutObject",
670
+ "s3:DeleteObject",
671
+ ],
672
+ Resource: `arn:aws:s3:::${s3BucketName}/*`,
673
+ },
674
+ {
675
+ Sid: "S3BucketList",
676
+ Effect: "Allow",
677
+ Action: ["s3:ListBucket", "s3:GetBucketLocation"],
678
+ Resource: `arn:aws:s3:::${s3BucketName}`,
679
+ },
680
+ ],
681
+ }),
682
+ );
683
+ }
684
+
685
+ if (opensearchDomainArn) {
686
+ await module.exports.attachInlineRolePolicy(
687
+ role,
688
+ "lab-env-opensearch-access",
689
+ JSON.stringify({
690
+ Version: "2012-10-17",
691
+ Statement: [
692
+ {
693
+ Sid: "OpenSearchAccess",
694
+ Effect: "Allow",
695
+ Action: [
696
+ "es:ESHttpGet",
697
+ "es:ESHttpPost",
698
+ "es:ESHttpPut",
699
+ "es:ESHttpDelete",
700
+ "es:ESHttpHead",
701
+ "es:ESHttpPatch",
702
+ ],
703
+ Resource: `${opensearchDomainArn}/*`,
704
+ },
705
+ ],
706
+ }),
707
+ );
708
+ }
709
+
710
+ return role;
711
+ };
712
+
713
+ module.exports.ensureOpenSearchServiceRole = async () => {
714
+ try {
715
+ await Spinner.prototype.simple(
716
+ "Ensuring OpenSearch service-linked role exists",
717
+ () => {
718
+ return getClient().send(
719
+ new CreateServiceLinkedRoleCommand({
720
+ AWSServiceName: "opensearchservice.amazonaws.com",
721
+ }),
722
+ );
723
+ },
724
+ );
725
+ } catch (e) {
726
+ if (
727
+ e.Code !== "InvalidInput" &&
728
+ e.name !== "InvalidInputException" &&
729
+ e.name !== "InvalidInput"
730
+ ) {
731
+ throw e;
732
+ }
733
+ // Role already exists — safe to continue
734
+ }
735
+ };
736
+
737
+ module.exports.removeEnvironmentInstanceProfile = async (ebSlug) => {
738
+ const role = `${ebSlug}-role`;
739
+
740
+ try {
741
+ await module.exports.getRole(role);
742
+ } catch {
743
+ return;
744
+ }
745
+
746
+ const attachedPolicies = await Spinner.prototype.simple(
747
+ `Listing attached policies for ${role}`,
748
+ () => {
749
+ return getClient().send(
750
+ new ListAttachedRolePoliciesCommand({ RoleName: role }),
751
+ );
752
+ },
753
+ );
754
+
755
+ for (const policy of attachedPolicies.AttachedPolicies || []) {
756
+ await Spinner.prototype.simple(
757
+ `Detaching policy ${policy.PolicyName}`,
758
+ () => {
759
+ return getClient().send(
760
+ new DetachRolePolicyCommand({
761
+ RoleName: role,
762
+ PolicyArn: policy.PolicyArn,
763
+ }),
764
+ );
765
+ },
766
+ );
767
+ }
768
+
769
+ const inlinePolicies = await Spinner.prototype.simple(
770
+ `Listing inline policies for ${role}`,
771
+ () => {
772
+ return getClient().send(
773
+ new ListRolePoliciesCommand({ RoleName: role }),
774
+ );
775
+ },
776
+ );
777
+
778
+ for (const policyName of inlinePolicies.PolicyNames || []) {
779
+ await Spinner.prototype.simple(
780
+ `Deleting inline policy ${policyName}`,
781
+ () => {
782
+ return getClient().send(
783
+ new DeleteRolePolicyCommand({
784
+ RoleName: role,
785
+ PolicyName: policyName,
786
+ }),
787
+ );
788
+ },
789
+ );
790
+ }
791
+
792
+ try {
793
+ await Spinner.prototype.simple(
794
+ `Removing role from instance profile ${role}`,
795
+ () => {
796
+ return getClient().send(
797
+ new RemoveRoleFromInstanceProfileCommand({
798
+ RoleName: role,
799
+ InstanceProfileName: role,
800
+ }),
801
+ );
802
+ },
803
+ );
804
+ } catch {
805
+ /* empty */
806
+ }
807
+
808
+ try {
809
+ await Spinner.prototype.simple(
810
+ `Deleting instance profile ${role}`,
811
+ () => {
812
+ return getClient().send(
813
+ new DeleteInstanceProfileCommand({
814
+ InstanceProfileName: role,
815
+ }),
816
+ );
817
+ },
818
+ );
819
+ } catch {
820
+ /* empty */
821
+ }
822
+
823
+ await Spinner.prototype.simple(`Deleting role ${role}`, () => {
824
+ return getClient().send(new DeleteRoleCommand({ RoleName: role }));
825
+ });
826
+ };