@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.
@@ -76,7 +76,7 @@ module.exports.stack = {
76
76
  module.exports.client = {
77
77
  type: "list",
78
78
  name: "client",
79
- message: "Which AWS account should this be deployed too?",
79
+ message: "Which AWS account should be used?",
80
80
  choices: clients.sort(),
81
81
  default: 0,
82
82
  loop: false,
@@ -13,6 +13,9 @@ const {
13
13
  PublishFunctionCommand,
14
14
  DeleteFunctionCommand,
15
15
  DescribeFunctionCommand,
16
+ ListDistributionsCommand,
17
+ ListTagsForResourceCommand,
18
+ ListInvalidationsCommand,
16
19
  } = require("@aws-sdk/client-cloudfront");
17
20
  const fs = require("fs");
18
21
  const { merge } = require("lodash");
@@ -381,3 +384,53 @@ module.exports.removeCloudFrontFunctionAssociation = async (Id) => {
381
384
  },
382
385
  );
383
386
  };
387
+
388
+ module.exports.listDistributions = async () => {
389
+ const client = new CloudFrontClient({});
390
+
391
+ let distributions = [];
392
+ let marker;
393
+
394
+ do {
395
+ const res = await client.send(
396
+ new ListDistributionsCommand({
397
+ ...(marker ? { Marker: marker } : {}),
398
+ }),
399
+ );
400
+
401
+ const items = res.DistributionList?.Items || [];
402
+ distributions = distributions.concat(items);
403
+ marker = res.DistributionList?.IsTruncated
404
+ ? res.DistributionList.NextMarker
405
+ : undefined;
406
+ } while (marker);
407
+
408
+ return distributions;
409
+ };
410
+
411
+ module.exports.getDistributionTags = async (arn) => {
412
+ const client = new CloudFrontClient({});
413
+
414
+ const res = await client.send(
415
+ new ListTagsForResourceCommand({ Resource: arn }),
416
+ );
417
+
418
+ return res.Tags?.Items || [];
419
+ };
420
+
421
+ module.exports.getLatestInvalidationDate = async (distributionId) => {
422
+ const client = new CloudFrontClient({});
423
+
424
+ const res = await client.send(
425
+ new ListInvalidationsCommand({
426
+ DistributionId: distributionId,
427
+ MaxItems: 1,
428
+ }),
429
+ );
430
+
431
+ const items = res.InvalidationList?.Items || [];
432
+
433
+ if (!items.length) return null;
434
+
435
+ return items[0].CreateTime;
436
+ };
@@ -4,9 +4,13 @@ const {
4
4
  ElasticBeanstalkClient,
5
5
  CreateApplicationCommand,
6
6
  DeleteApplicationCommand,
7
+ DeleteApplicationVersionCommand,
7
8
  CreateEnvironmentCommand,
8
9
  TerminateEnvironmentCommand,
9
10
  DescribeEnvironmentsCommand,
11
+ DescribeApplicationsCommand,
12
+ DescribeApplicationVersionsCommand,
13
+ CreateApplicationVersionCommand,
10
14
  ListAvailableSolutionStacksCommand,
11
15
  } = require("@aws-sdk/client-elastic-beanstalk");
12
16
  const { Spinner, poll } = require("../../libs/utilities");
@@ -160,3 +164,69 @@ module.exports.copyElasticBeanstalkConfigs = async (configurations) => {
160
164
 
161
165
  fs.outputFileSync(`.elasticbeanstalk/config.yml`, eb.config());
162
166
  };
167
+
168
+ module.exports.listApplications = async () => {
169
+ const client = new ElasticBeanstalkClient({});
170
+
171
+ const res = await Spinner.prototype.simple(
172
+ `Retrieving elasticbeanstalk applications`,
173
+ () => {
174
+ return client.send(new DescribeApplicationsCommand({}));
175
+ },
176
+ );
177
+
178
+ return res.Applications || [];
179
+ };
180
+
181
+ module.exports.listApplicationVersions = async (applicationName) => {
182
+ const client = new ElasticBeanstalkClient({});
183
+
184
+ let versions = [];
185
+ let nextToken;
186
+
187
+ do {
188
+ const res = await client.send(
189
+ new DescribeApplicationVersionsCommand({
190
+ ApplicationName: applicationName,
191
+ ...(nextToken ? { NextToken: nextToken } : {}),
192
+ }),
193
+ );
194
+
195
+ versions = versions.concat(res.ApplicationVersions || []);
196
+ nextToken = res.NextToken;
197
+ } while (nextToken);
198
+
199
+ return versions;
200
+ };
201
+
202
+ module.exports.createApplicationVersion = async (
203
+ applicationName,
204
+ versionLabel,
205
+ ) => {
206
+ const client = new ElasticBeanstalkClient({});
207
+
208
+ const res = await client.send(
209
+ new CreateApplicationVersionCommand({
210
+ ApplicationName: applicationName,
211
+ VersionLabel: versionLabel,
212
+ Process: false,
213
+ }),
214
+ );
215
+
216
+ return res;
217
+ };
218
+
219
+ module.exports.deleteApplicationVersion = async (
220
+ applicationName,
221
+ versionLabel,
222
+ ) => {
223
+ const client = new ElasticBeanstalkClient({});
224
+
225
+ await client.send(
226
+ new DeleteApplicationVersionCommand({
227
+ ApplicationName: applicationName,
228
+ VersionLabel: versionLabel,
229
+ DeleteSourceBundle: true,
230
+ }),
231
+ );
232
+ };
@@ -137,6 +137,16 @@ module.exports.syncFWIAMPolicies = async (
137
137
  UserName,
138
138
  "arn:aws:iam::aws:policy/AmazonOpenSearchServiceFullAccess",
139
139
  );
140
+
141
+ await module.exports.attachIAMPolicy(
142
+ UserName,
143
+ "arn:aws:iam::aws:policy/AmazonOpenSearchServiceFullAccess",
144
+ );
145
+
146
+ await module.exports.attachIAMPolicy(
147
+ UserName,
148
+ "arn:aws:iam::aws:policy/AmazonElastiCacheFullAccess",
149
+ );
140
150
  }
141
151
 
142
152
  if (permissions.includes("manage-users")) {
@@ -472,6 +482,7 @@ module.exports.ensureEBManagedUpdateProfileExists = async () => {
472
482
  "cloudwatch:DeleteAlarms",
473
483
  "cloudwatch:DescribeAlarms",
474
484
  "cloudwatch:PutMetricData",
485
+ "elasticache:DescribeReplicationGroups",
475
486
  ],
476
487
  Resource: "*",
477
488
  },
package/eslint.config.js CHANGED
@@ -79,6 +79,7 @@ module.exports = defineConfig([
79
79
  fixture: "readonly",
80
80
  expect: "readonly",
81
81
  $: "readonly",
82
+ $$: "readonly",
82
83
  _: "readonly",
83
84
  },
84
85
  },
package/globals.js CHANGED
@@ -134,6 +134,7 @@ var opts = { encoding: "utf8", stdio: "inherit", shell: "/bin/bash" };
134
134
  const users = {
135
135
  core: "node",
136
136
  php: "php",
137
+ python: "py",
137
138
  };
138
139
 
139
140
  try {
@@ -233,7 +234,8 @@ if (composer && composer.require && composer.require["laravel/framework"]) {
233
234
  args[0] !== "key" &&
234
235
  args[0] !== "dekey" &&
235
236
  args[0] !== "lint" &&
236
- args[0] !== "workspace"
237
+ args[0] !== "workspace" &&
238
+ args[0] !== "prune"
237
239
  ) {
238
240
  unknownLabEnvConfig(platform, version);
239
241
  }
@@ -278,7 +280,8 @@ if (composer && composer.require && composer.require["laravel/framework"]) {
278
280
  args[0] !== "key" &&
279
281
  args[0] !== "dekey" &&
280
282
  args[0] !== "lint" &&
281
- args[0] !== "workspace"
283
+ args[0] !== "workspace" &&
284
+ args[0] !== "prune"
282
285
  ) {
283
286
  unknownLabEnvConfig(platform, version);
284
287
  }
@@ -352,7 +355,8 @@ if (process.env.FW_NEXT || forced) {
352
355
  args[0] !== "key" &&
353
356
  args[0] !== "dekey" &&
354
357
  args[0] !== "lint" &&
355
- args[0] !== "workspace"
358
+ args[0] !== "workspace" &&
359
+ args[0] !== "prune"
356
360
  ) {
357
361
  if (version) {
358
362
  unknownLabEnvConfig("core", version);
@@ -712,7 +716,8 @@ if (
712
716
  args[0] !== "key" &&
713
717
  args[0] !== "dekey" &&
714
718
  args[0] !== "lint" &&
715
- args[0] !== "workspace"
719
+ args[0] !== "workspace" &&
720
+ args[0] !== "prune"
716
721
  ) {
717
722
  if (!forced) {
718
723
  // Stop here if diagnosis either unset or outdated
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fishawack/lab-env",
3
- "version": "5.3.0",
3
+ "version": "5.4.0-beta.1",
4
4
  "description": "Docker manager for FW",
5
5
  "main": "cli.js",
6
6
  "scripts": {
@@ -0,0 +1,10 @@
1
+ ## Changelog
2
+
3
+ ### 1.1.1 (2026-03-11)
4
+ * [Bug] own venv dir so ci can remove correcty
5
+
6
+ ### 1.1.0 (2026-03-11)
7
+ * [Feat] python now uses named user and sets up like other projects
8
+
9
+ ### 1.0.0 (2025-07-21)
10
+ * [Misc] initial commit
@@ -2,6 +2,15 @@ FROM ghcr.io/astral-sh/uv:python3.13-bookworm AS development
2
2
 
3
3
  LABEL org.opencontainers.image.authors="Mike Mellor <mike.mellor@avalerehealth.com>"
4
4
 
5
+ # Add py user
6
+ RUN useradd -m -G www-data -s /bin/bash py
7
+
8
+ COPY entrypoint.sh /bin/entrypoint.sh
9
+ RUN chmod +x /bin/entrypoint.sh
10
+ ENTRYPOINT ["/bin/entrypoint.sh"]
11
+
12
+ CMD ["sleep infinity"]
13
+
5
14
  FROM fishawack/lab-env-python-0:latest AS production
6
15
 
7
16
  # Copy source code into container
@@ -5,10 +5,11 @@ services:
5
5
  target: development
6
6
  image: $FW_PYTHON_0_IMAGE:${FW_PYTHON_0_VERSION:-latest}
7
7
  working_dir: /workspace
8
- command: sleep infinity
9
8
  tty: true
10
9
  stdin_open: true
11
- user: "${USER_UID}:${USER_GID}"
10
+ init: true
11
+ networks:
12
+ - default
12
13
  environment:
13
14
  - PATH=/workspace/.venv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
14
15
  - UV_NO_CACHE=1
@@ -17,12 +18,18 @@ services:
17
18
  - PYTHONUNBUFFERED="1"
18
19
  - REPO=${REPO:-}
19
20
  - PORT_PY=${PORT_PY:-8000}
21
+ - FW_ROOT=${FW_ROOT:-}
20
22
  - USER_UID=${USER_UID:-0}
21
23
  - USER_GID=${USER_GID:-0}
22
24
  ports:
23
25
  - ${PORT_PY:-8000}:${PORT_PY:-8000}
24
26
  volumes:
25
27
  - $CWD/:/workspace
28
+ - venv:/workspace/.venv
29
+ - $FW_DIR/.ssh:/home/py/.ssh
26
30
  networks:
27
31
  default:
28
32
  driver: "bridge"
33
+ volumes:
34
+ venv:
35
+ driver: "local"
@@ -0,0 +1,24 @@
1
+ #!/bin/bash
2
+
3
+ # Setting $FW_ROOT will bypass the user switch to py
4
+ if [ -z "$FW_ROOT" ]; then
5
+ # Set py user id to match host users id so no permission issues outside of docker
6
+ usermod -u $USER_UID py &>/dev/null
7
+
8
+ # Fix access rights to stdout and stderr
9
+ chown py /proc/self/fd/{1,2}
10
+
11
+ # Own the .venv folder otherwise it'll be owned by root/previous py id which will prevent writing
12
+ chown py /workspace/.venv
13
+
14
+ # If bash command then start an non login interactive shell
15
+ if [[ "$@" == "bash" ]]; then
16
+ exec su py
17
+ # Otherwise pipe the command into the non login non interactive shell as a command to exec
18
+ else
19
+ exec su py -c "$@"
20
+ fi
21
+ else
22
+ echo "Accessing as root"
23
+ exec "$@"
24
+ fi
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "python",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "lab-env docker config for the python module",
5
5
  "scripts": {
6
6
  "preversion": "docker login",