@fishawack/lab-env 5.3.0-beta.3 → 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 CHANGED
@@ -1,5 +1,50 @@
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
+
27
+ ### 5.3.0 (2026-02-23)
28
+
29
+ #### Features
30
+
31
+ * can now customize headers for static provisions ([a3bc776](https://bitbucket.org/fishawackdigital/lab-env/commits/a3bc776c6acca3555de4ef5667ffdf9f8b37a518))
32
+ * can now specify redirects for use in cloudfront functions ([67706d7](https://bitbucket.org/fishawackdigital/lab-env/commits/67706d76300fc6743f7a5ceb9bf31748cf8acb31))
33
+ * snyk scans now run on all builds but only fail builds with snyk flag ([03975eb](https://bitbucket.org/fishawackdigital/lab-env/commits/03975eb8c9f0a19535f026f65b547ea76f3743e2))
34
+
35
+ #### Bug Fixes
36
+
37
+ * deprov now correctly removes the response function for static sites ([69fbc2b](https://bitbucket.org/fishawackdigital/lab-env/commits/69fbc2b8f60898d3d33416328752897dff6f752b))
38
+ * ignore submodules found in packages in linting reports ([14b9064](https://bitbucket.org/fishawackdigital/lab-env/commits/14b9064db699a7bd57c33fcb851c7c651c9cead6))
39
+ * issue with lab-env calling lab-env and concatanating repo env variable ([03313c3](https://bitbucket.org/fishawackdigital/lab-env/commits/03313c3c43c793222620170c381af05ec4b1c348))
40
+ * prompt if redirects are needed first ([e087bf0](https://bitbucket.org/fishawackdigital/lab-env/commits/e087bf0f6d86c543f31d9f0c401793222f8f0d9a))
41
+ * set aws client on key command ([3b15512](https://bitbucket.org/fishawackdigital/lab-env/commits/3b15512b168d4ba35871047fd0ea9230cf394804))
42
+ * use correct prod base image on python images ([db0ace4](https://bitbucket.org/fishawackdigital/lab-env/commits/db0ace419e8c635c727aa33330a3156c0956af0b))
43
+
44
+ #### Build Updates
45
+
46
+ * **core/1:** Bumped lab-env-core-1 ([e3e151a](https://bitbucket.org/fishawackdigital/lab-env/commits/e3e151aacb75c360f36f7b8303966a99f1c202d0))
47
+
3
48
  ### 5.3.0-beta.3 (2026-02-21)
4
49
 
5
50
  #### Features
@@ -43,6 +43,8 @@ describe("provision", () => {
43
43
 
44
44
  describe("fullstack", () => {
45
45
  before(async () => {
46
+ setAWSClientDefaults(account);
47
+
46
48
  fetch = (await import("node-fetch")).default;
47
49
 
48
50
  process.env.REPO = repo;
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
@@ -169,6 +169,7 @@ const args = hideBin(process.argv);
169
169
  "delete",
170
170
  "key",
171
171
  "dekey",
172
+ "prune",
172
173
  ].forEach((d) =>
173
174
  cli.command(...require(`./commands/create/cmds/${d}.js`)),
174
175
  );
@@ -97,7 +97,6 @@ module.exports = [
97
97
 
98
98
  let res = await aws.iam.createFWIAMUser(
99
99
  `fw-automation-${user}`,
100
- client,
101
100
  _.config.users.find((d) => d.username === user).permissions,
102
101
  );
103
102