@fishawack/lab-env 5.7.0-beta.3 → 5.7.0-beta.5
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/CHANGELOG.md +17 -0
- package/commands/create/libs/parallel-runner.js +88 -14
- package/commands/create/libs/vars.js +3 -3
- package/commands/create/services/aws/iam.js +19 -5
- package/commands/create/services/aws/index.js +3 -10
- package/commands/scan.js +1 -1
- package/globals.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
### 5.7.0-beta.5 (2026-07-03)
|
|
4
|
+
|
|
5
|
+
#### Bug Fixes
|
|
6
|
+
|
|
7
|
+
* allow rekey outside of projects ([94b511b](https://bitbucket.org/fishawackdigital/lab-env/commits/94b511b629fbf80b842a9ab5db9f3671f1f603f0))
|
|
8
|
+
* retry on rate limits and aws errors on key commands ([0244221](https://bitbucket.org/fishawackdigital/lab-env/commits/0244221b8a263256f015380280024c862213b885))
|
|
9
|
+
* safe check for misc bitbucket vars ([cee035c](https://bitbucket.org/fishawackdigital/lab-env/commits/cee035c25f927d1b5f55c00f10fa35f08733e4bf))
|
|
10
|
+
|
|
11
|
+
### 5.7.0-beta.4 (2026-07-03)
|
|
12
|
+
|
|
13
|
+
#### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* added missing ses permission for fullstack roles ([9da2562](https://bitbucket.org/fishawackdigital/lab-env/commits/9da256264ef3ac923bcf4acf0b531e28c3aa154f))
|
|
16
|
+
* correctly tag iam ec2 role for eb with tags ([204aced](https://bitbucket.org/fishawackdigital/lab-env/commits/204acedd6f63af0b6c72875b12b6e97660dfa6a3))
|
|
17
|
+
* safe check the fail property on scans as sometimes the file doesnt exist ([e5b5e39](https://bitbucket.org/fishawackdigital/lab-env/commits/e5b5e39251e6c1c22ec7f9cf155ad5c86492e9cb))
|
|
18
|
+
* session domain now the domain given exactly ([8071c91](https://bitbucket.org/fishawackdigital/lab-env/commits/8071c911a54c869661469fe2fdcdcc3ffc10cb2b))
|
|
19
|
+
|
|
3
20
|
### 5.7.0-beta.3 (2026-07-01)
|
|
4
21
|
|
|
5
22
|
#### Features
|
|
@@ -3,6 +3,33 @@ const chalk = require("chalk");
|
|
|
3
3
|
const { Spinner } = require("./utilities");
|
|
4
4
|
|
|
5
5
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
6
|
+
const MAX_RETRIES = 15;
|
|
7
|
+
const INITIAL_DELAY = 2000;
|
|
8
|
+
const MAX_DELAY = 60000;
|
|
9
|
+
|
|
10
|
+
const RETRYABLE_ERRORS = [
|
|
11
|
+
"Throttling",
|
|
12
|
+
"ThrottlingException",
|
|
13
|
+
"TooManyRequestsException",
|
|
14
|
+
"RequestLimitExceeded",
|
|
15
|
+
"ServiceUnavailable",
|
|
16
|
+
"InternalFailure",
|
|
17
|
+
"RequestTimeout",
|
|
18
|
+
"RequestTimeoutException",
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function isRetryable(err) {
|
|
22
|
+
if (
|
|
23
|
+
RETRYABLE_ERRORS.includes(err.name) ||
|
|
24
|
+
RETRYABLE_ERRORS.includes(err.Code)
|
|
25
|
+
) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const statusCode = err.$metadata?.httpStatusCode;
|
|
30
|
+
|
|
31
|
+
return statusCode === 429 || statusCode === 503 || statusCode === 500;
|
|
32
|
+
}
|
|
6
33
|
|
|
7
34
|
function formatElapsed(ms) {
|
|
8
35
|
const secs = Math.floor(ms / 1000);
|
|
@@ -78,7 +105,20 @@ module.exports = async function parallelRunner(
|
|
|
78
105
|
continue;
|
|
79
106
|
}
|
|
80
107
|
|
|
81
|
-
if (state.status === "
|
|
108
|
+
if (state.status === "retrying") {
|
|
109
|
+
const remaining = Math.max(
|
|
110
|
+
0,
|
|
111
|
+
Math.ceil(
|
|
112
|
+
(state.retryDelay - (Date.now() - state.retryStart)) /
|
|
113
|
+
1000,
|
|
114
|
+
),
|
|
115
|
+
);
|
|
116
|
+
lines.push(
|
|
117
|
+
chalk.yellow(
|
|
118
|
+
` ↻ ${verb}: ${user} - ${state.client} (${state.batchesDone + 1}/${totalBatches}) retry ${state.retryAttempt}/${MAX_RETRIES} ${remaining}s`,
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
} else if (state.status === "active") {
|
|
82
122
|
const taskElapsed = Date.now() - state.taskStart;
|
|
83
123
|
let suffix = "";
|
|
84
124
|
if (taskElapsed > 5000) {
|
|
@@ -130,24 +170,58 @@ module.exports = async function parallelRunner(
|
|
|
130
170
|
const groupUsers = group.filter((u) => batch.users.includes(u));
|
|
131
171
|
|
|
132
172
|
await Promise.all(
|
|
133
|
-
groupUsers.map(async (user) => {
|
|
173
|
+
groupUsers.map(async (user, userIndex) => {
|
|
134
174
|
const state = groupStates.get(user);
|
|
135
175
|
state.client = batch.client;
|
|
136
|
-
state.status = "active";
|
|
137
176
|
state.taskStart = Date.now();
|
|
138
177
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
178
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
179
|
+
state.status = "active";
|
|
180
|
+
state.taskStart = Date.now();
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const res = await batch.fn(user);
|
|
184
|
+
results
|
|
185
|
+
.get(user)
|
|
186
|
+
.push({ client: batch.client, res });
|
|
187
|
+
state.status = "done";
|
|
188
|
+
state.retryAttempt = 0;
|
|
189
|
+
break;
|
|
190
|
+
} catch (err) {
|
|
191
|
+
if (!isRetryable(err) || attempt === MAX_RETRIES) {
|
|
192
|
+
state.status = "error";
|
|
193
|
+
state.hasError = true;
|
|
194
|
+
state.errorMsg = err.message || String(err);
|
|
195
|
+
errors.push({
|
|
196
|
+
user,
|
|
197
|
+
client: batch.client,
|
|
198
|
+
error: err,
|
|
199
|
+
});
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let delay = Math.min(
|
|
204
|
+
INITIAL_DELAY * Math.pow(2, attempt),
|
|
205
|
+
MAX_DELAY,
|
|
206
|
+
);
|
|
207
|
+
// Stagger retries per user to avoid thundering herd
|
|
208
|
+
delay +=
|
|
209
|
+
Math.floor(Math.random() * delay * 0.3) +
|
|
210
|
+
userIndex * 1000;
|
|
211
|
+
|
|
212
|
+
state.status = "retrying";
|
|
213
|
+
state.retryAttempt = attempt + 1;
|
|
214
|
+
state.retryDelay = delay;
|
|
215
|
+
state.retryStart = Date.now();
|
|
216
|
+
throttleCount++;
|
|
217
|
+
|
|
218
|
+
await new Promise((resolve) =>
|
|
219
|
+
setTimeout(resolve, delay),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
150
222
|
}
|
|
223
|
+
|
|
224
|
+
state.batchesDone++;
|
|
151
225
|
}),
|
|
152
226
|
);
|
|
153
227
|
}
|
|
@@ -68,17 +68,17 @@ const bitbucketApi = (module.exports.bitbucketApi =
|
|
|
68
68
|
"https://api.bitbucket.org/2.0/repositories");
|
|
69
69
|
|
|
70
70
|
module.exports.apis = {
|
|
71
|
-
bbWorkspaceAPI: `${bitbucketApi}/${misc
|
|
71
|
+
bbWorkspaceAPI: `${bitbucketApi}/${misc?.bitbucket?.workspace}`,
|
|
72
72
|
};
|
|
73
73
|
|
|
74
74
|
// URLs
|
|
75
75
|
module.exports.urls = {
|
|
76
|
-
bitbucketSSH: `git@bitbucket.org:${misc
|
|
76
|
+
bitbucketSSH: `git@bitbucket.org:${misc?.bitbucket?.workspace}`,
|
|
77
77
|
};
|
|
78
78
|
|
|
79
79
|
// Request Headers
|
|
80
80
|
module.exports.headers = {
|
|
81
|
-
bbHeaders: encode(misc
|
|
81
|
+
bbHeaders: encode(misc?.bitbucket?.username, misc?.bitbucket?.token),
|
|
82
82
|
};
|
|
83
83
|
|
|
84
84
|
// Required global node modules
|
|
@@ -170,6 +170,7 @@ module.exports.syncFWIAMPolicies = async (
|
|
|
170
170
|
"arn:aws:iam::aws:policy/AmazonRDSFullAccess",
|
|
171
171
|
"arn:aws:iam::aws:policy/AmazonElastiCacheFullAccess",
|
|
172
172
|
"arn:aws:iam::aws:policy/SecretsManagerReadWrite",
|
|
173
|
+
"arn:aws:iam::aws:policy/AmazonSESFullAccess",
|
|
173
174
|
);
|
|
174
175
|
}
|
|
175
176
|
|
|
@@ -356,12 +357,20 @@ module.exports.getRole = async (RoleName) => {
|
|
|
356
357
|
return res;
|
|
357
358
|
};
|
|
358
359
|
|
|
359
|
-
module.exports.createRole = async (
|
|
360
|
+
module.exports.createRole = async (
|
|
361
|
+
RoleName,
|
|
362
|
+
AssumeRolePolicyDocument,
|
|
363
|
+
Tags,
|
|
364
|
+
) => {
|
|
360
365
|
let res = await Spinner.prototype.simple(
|
|
361
366
|
`Creating the role ${RoleName}`,
|
|
362
367
|
() => {
|
|
363
368
|
return getClient().send(
|
|
364
|
-
new CreateRoleCommand({
|
|
369
|
+
new CreateRoleCommand({
|
|
370
|
+
RoleName,
|
|
371
|
+
AssumeRolePolicyDocument,
|
|
372
|
+
...(Tags && Tags.length ? { Tags } : {}),
|
|
373
|
+
}),
|
|
365
374
|
);
|
|
366
375
|
},
|
|
367
376
|
);
|
|
@@ -369,12 +378,15 @@ module.exports.createRole = async (RoleName, AssumeRolePolicyDocument) => {
|
|
|
369
378
|
return res;
|
|
370
379
|
};
|
|
371
380
|
|
|
372
|
-
module.exports.createInstanceProfile = async (InstanceProfileName) => {
|
|
381
|
+
module.exports.createInstanceProfile = async (InstanceProfileName, Tags) => {
|
|
373
382
|
let res = await Spinner.prototype.simple(
|
|
374
383
|
`Creating the instance profile ${InstanceProfileName}`,
|
|
375
384
|
() => {
|
|
376
385
|
return getClient().send(
|
|
377
|
-
new CreateInstanceProfileCommand({
|
|
386
|
+
new CreateInstanceProfileCommand({
|
|
387
|
+
InstanceProfileName,
|
|
388
|
+
...(Tags && Tags.length ? { Tags } : {}),
|
|
389
|
+
}),
|
|
378
390
|
);
|
|
379
391
|
},
|
|
380
392
|
);
|
|
@@ -588,6 +600,7 @@ module.exports.ensureEnvironmentInstanceProfile = async (
|
|
|
588
600
|
ebSlug,
|
|
589
601
|
s3BucketName,
|
|
590
602
|
opensearchDomainArn,
|
|
603
|
+
tags,
|
|
591
604
|
) => {
|
|
592
605
|
const role = `${ebSlug}-role`;
|
|
593
606
|
|
|
@@ -608,9 +621,10 @@ module.exports.ensureEnvironmentInstanceProfile = async (
|
|
|
608
621
|
},
|
|
609
622
|
],
|
|
610
623
|
}),
|
|
624
|
+
tags,
|
|
611
625
|
);
|
|
612
626
|
|
|
613
|
-
await module.exports.createInstanceProfile(role);
|
|
627
|
+
await module.exports.createInstanceProfile(role, tags);
|
|
614
628
|
await module.exports.attachRoleToInstanceProfile(role, role);
|
|
615
629
|
}
|
|
616
630
|
|
|
@@ -814,6 +814,7 @@ module.exports.fullstack = async (
|
|
|
814
814
|
name,
|
|
815
815
|
s3Slug,
|
|
816
816
|
opensearchDomainArn,
|
|
817
|
+
tags,
|
|
817
818
|
);
|
|
818
819
|
} else {
|
|
819
820
|
await aws.iam.ensureEBInstanceProfileExists();
|
|
@@ -960,11 +961,7 @@ module.exports.fullstack = async (
|
|
|
960
961
|
...mailCreds,
|
|
961
962
|
APP_NAME: appName,
|
|
962
963
|
APP_DOMAIN: appDomain,
|
|
963
|
-
SESSION_DOMAIN: appDomain
|
|
964
|
-
? appDomain.split(".").length > 2
|
|
965
|
-
? appDomain.split(".").slice(1).join(".")
|
|
966
|
-
: appDomain
|
|
967
|
-
: undefined,
|
|
964
|
+
SESSION_DOMAIN: appDomain || undefined,
|
|
968
965
|
},
|
|
969
966
|
);
|
|
970
967
|
|
|
@@ -1099,11 +1096,7 @@ module.exports.fullstack = async (
|
|
|
1099
1096
|
...cacheData,
|
|
1100
1097
|
APP_NAME: appName,
|
|
1101
1098
|
APP_DOMAIN: appDomain,
|
|
1102
|
-
SESSION_DOMAIN: appDomain
|
|
1103
|
-
? appDomain.split(".").length > 2
|
|
1104
|
-
? appDomain.split(".").slice(1).join(".")
|
|
1105
|
-
: appDomain
|
|
1106
|
-
: undefined,
|
|
1099
|
+
SESSION_DOMAIN: appDomain || undefined,
|
|
1107
1100
|
},
|
|
1108
1101
|
);
|
|
1109
1102
|
|
package/commands/scan.js
CHANGED
|
@@ -32,7 +32,7 @@ const run = () => {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
if (failed) {
|
|
35
|
-
if (_.coreConfig
|
|
35
|
+
if (_.coreConfig?.attributes?.failOnScanErrors) {
|
|
36
36
|
throw new Error(
|
|
37
37
|
"Scan found issues. Failing the build as failOnScanErrors flag is set to true.",
|
|
38
38
|
);
|
package/globals.js
CHANGED
|
@@ -279,6 +279,7 @@ if (composer && composer.require && composer.require["laravel/framework"]) {
|
|
|
279
279
|
args[0] !== "new" &&
|
|
280
280
|
args[0] !== "key" &&
|
|
281
281
|
args[0] !== "dekey" &&
|
|
282
|
+
args[0] !== "rekey" &&
|
|
282
283
|
args[0] !== "lint" &&
|
|
283
284
|
args[0] !== "workspace" &&
|
|
284
285
|
args[0] !== "prune"
|