@fishawack/lab-env 5.3.0 → 5.4.0-beta.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/CHANGELOG.md +24 -0
- package/_Test/provision.js +2 -0
- package/_Test/prune.js +291 -0
- package/cli.js +1 -0
- package/commands/create/cmds/key.js +0 -1
- package/commands/create/cmds/prune.js +723 -0
- package/commands/create/libs/prompts.js +1 -1
- package/commands/create/services/aws/cloudfront.js +53 -0
- package/commands/create/services/aws/elasticbeanstalk.js +70 -0
- package/commands/create/services/aws/iam.js +11 -0
- package/eslint.config.js +1 -0
- package/globals.js +9 -4
- package/package.json +1 -1
- package/python/0/CHANGELOG.md +10 -0
- package/python/0/Dockerfile +9 -0
- package/python/0/docker-compose.yml +9 -2
- package/python/0/entrypoint.sh +24 -0
- package/python/0/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
## Changelog
|
|
2
2
|
|
|
3
|
+
### 5.4.0-beta.1 (2026-03-12)
|
|
4
|
+
|
|
5
|
+
#### Features
|
|
6
|
+
|
|
7
|
+
* added a prune command ([0775617](https://bitbucket.org/fishawackdigital/lab-env/commits/0775617e763d9833e78633df48d5560ac4bb4ea5))
|
|
8
|
+
* added more consistent prune flags to handle all clients and regions at once ([c2ff371](https://bitbucket.org/fishawackdigital/lab-env/commits/c2ff37189aa137291d1dc906b187ab808f0ec3c2))
|
|
9
|
+
* added new cloudfront methods ([8c9acf3](https://bitbucket.org/fishawackdigital/lab-env/commits/8c9acf39899147affb95476fd89b967eea57826c))
|
|
10
|
+
* created prune command for cloudfront and application version pruning ([be3ba54](https://bitbucket.org/fishawackdigital/lab-env/commits/be3ba5468187e5733a94e0bd266c8dfc2748e821))
|
|
11
|
+
* ignore double dollar for webdriverio shorthand ([044c457](https://bitbucket.org/fishawackdigital/lab-env/commits/044c4570c1d09161eb97835e9d1947d046866e1a))
|
|
12
|
+
|
|
13
|
+
#### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* add missing elasticcache permission on eb managed update profile ([a21bf3d](https://bitbucket.org/fishawackdigital/lab-env/commits/a21bf3de4e4014adde912711e7d47ed9b25904f2))
|
|
16
|
+
* add retrys and backoff and store the client and region on delete command ([36024b9](https://bitbucket.org/fishawackdigital/lab-env/commits/36024b96fe1ba42054efd280c08cb411dfc9a16e))
|
|
17
|
+
* added elastic cache to fulsltack deploy permissions ([c94dc47](https://bitbucket.org/fishawackdigital/lab-env/commits/c94dc47a971a6fe6818127573d306a9bcf28b19f))
|
|
18
|
+
* own venv dir so ci can remove correcty ([9b42c77](https://bitbucket.org/fishawackdigital/lab-env/commits/9b42c7729411142101b2f5b2f07f79a5b8363178))
|
|
19
|
+
* python now maps like other containers with user and ssh for private packages ([fb04737](https://bitbucket.org/fishawackdigital/lab-env/commits/fb047378844d44b5f79dc67e0d089819cfdde82f))
|
|
20
|
+
* remove client from iam signature as it broke permissions ([777f58f](https://bitbucket.org/fishawackdigital/lab-env/commits/777f58fa20403940ba2cd6cb4aa384fbf7f4553a))
|
|
21
|
+
|
|
22
|
+
#### Build Updates
|
|
23
|
+
|
|
24
|
+
* bumped fishawack/lab-env-python-0 to 1.1.0 ([db2ffd1](https://bitbucket.org/fishawackdigital/lab-env/commits/db2ffd1a9fe9be7029fc1b434072bbbccaa670b4))
|
|
25
|
+
* bumped fishawack/lab-env-python-0 to 1.1.1 ([9a70106](https://bitbucket.org/fishawackdigital/lab-env/commits/9a70106dd9778ee23ab04039eed692788b7199c8))
|
|
26
|
+
|
|
3
27
|
### 5.3.0 (2026-02-23)
|
|
4
28
|
|
|
5
29
|
#### Features
|
package/_Test/provision.js
CHANGED
package/_Test/prune.js
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const expect = require("chai").expect;
|
|
4
|
+
const aws = require("../commands/create/services/aws/index.js");
|
|
5
|
+
const {
|
|
6
|
+
setAWSClientDefaults,
|
|
7
|
+
} = require("../commands/create/services/aws/misc.js");
|
|
8
|
+
|
|
9
|
+
const APP_NAME = "lab-env-prune-test";
|
|
10
|
+
const VERSION_COUNT = 15;
|
|
11
|
+
const VERSIONS_TO_KEEP = 10;
|
|
12
|
+
const STALE_MONTHS = 3;
|
|
13
|
+
const ACCOUNT = "test";
|
|
14
|
+
const REGION = "us-east-1";
|
|
15
|
+
|
|
16
|
+
describe("prune", () => {
|
|
17
|
+
before(async () => {
|
|
18
|
+
setAWSClientDefaults(ACCOUNT, REGION);
|
|
19
|
+
|
|
20
|
+
await aws.elasticbeanstalk.createElasticBeanstalkApplication(APP_NAME);
|
|
21
|
+
|
|
22
|
+
for (let i = 1; i <= VERSION_COUNT; i++) {
|
|
23
|
+
await aws.elasticbeanstalk.createApplicationVersion(
|
|
24
|
+
APP_NAME,
|
|
25
|
+
`v${i}`,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("Should list all applications in a given account and region", async () => {
|
|
31
|
+
setAWSClientDefaults(ACCOUNT, REGION);
|
|
32
|
+
|
|
33
|
+
const applications = await aws.elasticbeanstalk.listApplications();
|
|
34
|
+
const names = applications.map((app) => app.ApplicationName);
|
|
35
|
+
|
|
36
|
+
expect(names).to.include(APP_NAME);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("Should list all application versions for a specific app", async () => {
|
|
40
|
+
const versions =
|
|
41
|
+
await aws.elasticbeanstalk.listApplicationVersions(APP_NAME);
|
|
42
|
+
|
|
43
|
+
expect(versions).to.have.lengthOf(VERSION_COUNT);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("Should identify prunable versions keeping the 10 most recent", async () => {
|
|
47
|
+
const versions =
|
|
48
|
+
await aws.elasticbeanstalk.listApplicationVersions(APP_NAME);
|
|
49
|
+
|
|
50
|
+
versions.sort(
|
|
51
|
+
(a, b) => new Date(b.DateCreated) - new Date(a.DateCreated),
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const keep = versions.slice(0, VERSIONS_TO_KEEP);
|
|
55
|
+
const prunable = versions.slice(VERSIONS_TO_KEEP);
|
|
56
|
+
|
|
57
|
+
expect(keep).to.have.lengthOf(VERSIONS_TO_KEEP);
|
|
58
|
+
expect(prunable).to.have.lengthOf(VERSION_COUNT - VERSIONS_TO_KEEP);
|
|
59
|
+
|
|
60
|
+
const oldestKept = new Date(
|
|
61
|
+
keep[keep.length - 1].DateCreated,
|
|
62
|
+
).getTime();
|
|
63
|
+
const newestPrunable = new Date(prunable[0].DateCreated).getTime();
|
|
64
|
+
|
|
65
|
+
expect(oldestKept).to.be.at.least(newestPrunable);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("Should delete prunable application versions", async () => {
|
|
69
|
+
const versions =
|
|
70
|
+
await aws.elasticbeanstalk.listApplicationVersions(APP_NAME);
|
|
71
|
+
|
|
72
|
+
versions.sort(
|
|
73
|
+
(a, b) => new Date(b.DateCreated) - new Date(a.DateCreated),
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const prunable = versions.slice(VERSIONS_TO_KEEP);
|
|
77
|
+
|
|
78
|
+
expect(prunable.length).to.be.greaterThan(0);
|
|
79
|
+
|
|
80
|
+
for (const v of prunable) {
|
|
81
|
+
await aws.elasticbeanstalk.deleteApplicationVersion(
|
|
82
|
+
APP_NAME,
|
|
83
|
+
v.VersionLabel,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const remaining =
|
|
88
|
+
await aws.elasticbeanstalk.listApplicationVersions(APP_NAME);
|
|
89
|
+
|
|
90
|
+
expect(remaining).to.have.lengthOf(VERSIONS_TO_KEEP);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("Should have only the most recent versions remaining after prune", async () => {
|
|
94
|
+
const remaining =
|
|
95
|
+
await aws.elasticbeanstalk.listApplicationVersions(APP_NAME);
|
|
96
|
+
|
|
97
|
+
remaining.sort(
|
|
98
|
+
(a, b) => new Date(b.DateCreated) - new Date(a.DateCreated),
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const labels = remaining.map((v) => v.VersionLabel);
|
|
102
|
+
|
|
103
|
+
// v11-v15 should remain (the 5 newest created last)
|
|
104
|
+
for (
|
|
105
|
+
let i = VERSION_COUNT - VERSIONS_TO_KEEP + 1;
|
|
106
|
+
i <= VERSION_COUNT;
|
|
107
|
+
i++
|
|
108
|
+
) {
|
|
109
|
+
expect(labels).to.include(`v${i}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// v1-v5 should be gone (the 5 oldest created first)
|
|
113
|
+
for (let i = 1; i <= VERSION_COUNT - VERSIONS_TO_KEEP; i++) {
|
|
114
|
+
expect(labels).to.not.include(`v${i}`);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("Should handle switching accounts gracefully", async () => {
|
|
119
|
+
// Switching to a different region should not see our test app
|
|
120
|
+
setAWSClientDefaults(ACCOUNT, "eu-west-1");
|
|
121
|
+
|
|
122
|
+
const applications = await aws.elasticbeanstalk.listApplications();
|
|
123
|
+
const names = applications.map((app) => app.ApplicationName);
|
|
124
|
+
|
|
125
|
+
// Test app was created in us-east-1, should not appear in eu-west-1
|
|
126
|
+
// (unless it happens to exist there too — either way this validates
|
|
127
|
+
// that setAWSClientDefaults switches context correctly)
|
|
128
|
+
expect(names).to.be.an("array");
|
|
129
|
+
|
|
130
|
+
// Switch back for cleanup
|
|
131
|
+
setAWSClientDefaults(ACCOUNT, REGION);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
after(async () => {
|
|
135
|
+
try {
|
|
136
|
+
await aws.elasticbeanstalk.removeElasticBeanstalkApplication(
|
|
137
|
+
APP_NAME,
|
|
138
|
+
);
|
|
139
|
+
} catch {
|
|
140
|
+
/* empty */
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("cloudfront static distributions", () => {
|
|
145
|
+
const CF_REPO = "lab-env-prune-cf-test";
|
|
146
|
+
const CF_BRANCH = "development";
|
|
147
|
+
let cfDistributionId;
|
|
148
|
+
let cfName;
|
|
149
|
+
|
|
150
|
+
before(async () => {
|
|
151
|
+
setAWSClientDefaults(ACCOUNT, REGION);
|
|
152
|
+
|
|
153
|
+
cfName = aws.slug(CF_REPO, ACCOUNT, CF_BRANCH);
|
|
154
|
+
|
|
155
|
+
const result = await aws.static(
|
|
156
|
+
cfName,
|
|
157
|
+
[
|
|
158
|
+
{ Key: "repository", Value: CF_REPO },
|
|
159
|
+
{ Key: "environment", Value: CF_BRANCH },
|
|
160
|
+
{ Key: "automated", Value: "true" },
|
|
161
|
+
],
|
|
162
|
+
[],
|
|
163
|
+
CF_REPO,
|
|
164
|
+
CF_BRANCH,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
cfDistributionId = result.cloudfront;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("Should list distributions for a given account and region", async () => {
|
|
171
|
+
setAWSClientDefaults(ACCOUNT, REGION);
|
|
172
|
+
|
|
173
|
+
const distributions = await aws.cloudfront.listDistributions();
|
|
174
|
+
|
|
175
|
+
expect(distributions).to.be.an("array");
|
|
176
|
+
expect(distributions.length).to.be.greaterThan(0);
|
|
177
|
+
|
|
178
|
+
const ids = distributions.map((d) => d.Id);
|
|
179
|
+
|
|
180
|
+
expect(ids).to.include(cfDistributionId);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("Should fetch tags for a distribution", async () => {
|
|
184
|
+
const distributions = await aws.cloudfront.listDistributions();
|
|
185
|
+
const dist = distributions.find((d) => d.Id === cfDistributionId);
|
|
186
|
+
|
|
187
|
+
expect(dist).to.exist;
|
|
188
|
+
|
|
189
|
+
const tags = await aws.cloudfront.getDistributionTags(dist.ARN);
|
|
190
|
+
|
|
191
|
+
expect(tags).to.be.an("array");
|
|
192
|
+
|
|
193
|
+
const keys = tags.map((t) => t.Key);
|
|
194
|
+
|
|
195
|
+
expect(keys).to.include("automated");
|
|
196
|
+
expect(keys).to.include("environment");
|
|
197
|
+
expect(keys).to.include("repository");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("Should identify automated distributions via tags", () => {
|
|
201
|
+
const automated = [{ Key: "automated", Value: "true" }];
|
|
202
|
+
const manual = [{ Key: "environment", Value: "dev" }];
|
|
203
|
+
|
|
204
|
+
expect(automated.some((t) => t.Key === "automated")).to.be.true;
|
|
205
|
+
expect(manual.some((t) => t.Key === "automated")).to.be.false;
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("Should identify production distributions via tags", () => {
|
|
209
|
+
const prod = [{ Key: "environment", Value: "production" }];
|
|
210
|
+
const staging = [{ Key: "environment", Value: "staging" }];
|
|
211
|
+
const prodShort = [{ Key: "environment", Value: "prod" }];
|
|
212
|
+
const noEnv = [{ Key: "automated", Value: "true" }];
|
|
213
|
+
|
|
214
|
+
const isProduction = (tags) => {
|
|
215
|
+
const env = tags.find((t) => t.Key === "environment");
|
|
216
|
+
if (!env) return false;
|
|
217
|
+
return /prod/i.test(env.Value);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
expect(isProduction(prod)).to.be.true;
|
|
221
|
+
expect(isProduction(prodShort)).to.be.true;
|
|
222
|
+
expect(isProduction(staging)).to.be.false;
|
|
223
|
+
expect(isProduction(noEnv)).to.be.false;
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("Should identify stale distributions older than threshold", () => {
|
|
227
|
+
const now = new Date();
|
|
228
|
+
|
|
229
|
+
const staleDate = new Date(now);
|
|
230
|
+
staleDate.setMonth(staleDate.getMonth() - STALE_MONTHS - 1);
|
|
231
|
+
|
|
232
|
+
const recentDate = new Date(now);
|
|
233
|
+
recentDate.setMonth(recentDate.getMonth() - 1);
|
|
234
|
+
|
|
235
|
+
const isStale = (lastModified) => {
|
|
236
|
+
const cutoff = new Date();
|
|
237
|
+
cutoff.setMonth(cutoff.getMonth() - STALE_MONTHS);
|
|
238
|
+
return new Date(lastModified) < cutoff;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
expect(isStale(staleDate.toISOString())).to.be.true;
|
|
242
|
+
expect(isStale(recentDate.toISOString())).to.be.false;
|
|
243
|
+
expect(isStale(now.toISOString())).to.be.false;
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("Should not flag a recently created distribution as prunable", async () => {
|
|
247
|
+
const distributions = await aws.cloudfront.listDistributions();
|
|
248
|
+
const dist = distributions.find((d) => d.Id === cfDistributionId);
|
|
249
|
+
|
|
250
|
+
const cutoff = new Date();
|
|
251
|
+
cutoff.setMonth(cutoff.getMonth() - STALE_MONTHS);
|
|
252
|
+
|
|
253
|
+
// Just created, so it should be newer than cutoff
|
|
254
|
+
expect(new Date(dist.LastModifiedTime) >= cutoff).to.be.true;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("Should tear down a static distribution completely", async () => {
|
|
258
|
+
setAWSClientDefaults(ACCOUNT, REGION);
|
|
259
|
+
|
|
260
|
+
await aws.staticTerminate(
|
|
261
|
+
cfName,
|
|
262
|
+
CF_REPO,
|
|
263
|
+
CF_BRANCH,
|
|
264
|
+
cfDistributionId,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
// Allow propagation time
|
|
268
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
269
|
+
|
|
270
|
+
const distributions = await aws.cloudfront.listDistributions();
|
|
271
|
+
const ids = distributions.map((d) => d.Id);
|
|
272
|
+
|
|
273
|
+
expect(ids).to.not.include(cfDistributionId);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
after(async () => {
|
|
277
|
+
// Safety cleanup in case test failed mid-way
|
|
278
|
+
try {
|
|
279
|
+
setAWSClientDefaults(ACCOUNT, REGION);
|
|
280
|
+
await aws.staticTerminate(
|
|
281
|
+
cfName,
|
|
282
|
+
CF_REPO,
|
|
283
|
+
CF_BRANCH,
|
|
284
|
+
cfDistributionId,
|
|
285
|
+
);
|
|
286
|
+
} catch {
|
|
287
|
+
/* empty */
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
});
|
package/cli.js
CHANGED