@leverege/build-tools 2.114.0-DEVOP-578.2 → 2.114.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/README.md CHANGED
@@ -44,6 +44,55 @@ Provides: `color()`, `errorExit()`, `warnOnError()`, `exitOnError()`, `generateP
44
44
 
45
45
  ### Helm & Kubernetes Deployment
46
46
 
47
+ #### The "field is immutable" error and `helmup --mutate`
48
+
49
+ A routine `helmup` will occasionally fail with an error like:
50
+
51
+ ```
52
+ Error: UPGRADE FAILED: cannot patch "my-service" with kind Deployment:
53
+ Deployment.apps "my-service" is invalid: spec.selector: Invalid value: ...:
54
+ field is immutable
55
+ ```
56
+
57
+ **Why it happens.** Kubernetes enforces immutability on certain fields after a resource is first
58
+ created. The most common is `spec.selector` on a Deployment — the label selector that determines
59
+ which pods belong to the workload. Once set at creation time, it can never be patched in place;
60
+ any attempt to change it is rejected by the API server outright, regardless of how the change
61
+ arrives (kubectl apply, helm upgrade, etc.). Helm has no built-in way to recover from this on its
62
+ own.
63
+ ([Kubernetes docs — label selector updates](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#label-selector-updates))
64
+
65
+ At Leverege, this most commonly surfaces when a service chart is upgraded from the older
66
+ self-contained flat chart structure to the newer Leverege Base Chart library approach
67
+ (`leverege-base-charts`). As the platform's chart conventions have evolved and improved over the
68
+ years, migrating a chart with `init-my-chart` can alter the rendered selector labels, leaving the
69
+ live Deployment's immutable `spec.selector` incompatible with what helm now wants to apply.
70
+
71
+ **What `helmup --mutate` does.** Before invoking helm, helmup deletes the `Deployment` object
72
+ for the service by name (`kubectl delete deployments.apps <service>`) and waits for the deletion
73
+ to complete. Helm then runs its normal upgrade, which recreates the Deployment from scratch with
74
+ the new selector. Because the object is gone before helm runs, there is no immutable-field
75
+ conflict to hit.
76
+
77
+ helmup will ask for confirmation before deleting and prints a warning that brief downtime is
78
+ coming. The upgrade proceeds automatically once you confirm.
79
+
80
+ **Side effects to be aware of:**
81
+
82
+ - *Brief downtime* — pods are terminated when the Deployment is deleted and do not come back
83
+ until helm recreates the object. The duration is typically a few seconds.
84
+ - *LoadBalancers and static IPs are unaffected* — only the Deployment object is deleted; Services
85
+ (and their external IPs) remain untouched throughout.
86
+ - *StatefulSets are not handled* — `--mutate` targets `deployments.apps` only. If you hit an
87
+ immutable field on a StatefulSet (e.g. `spec.volumeClaimTemplates`), you must delete the
88
+ StatefulSet manually using `--cascade=orphan` to preserve PVCs, then re-run helmup normally.
89
+ ([Kubernetes docs — StatefulSet limitations](https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#limitations))
90
+
91
+ In short: reach for `--mutate` whenever you see the immutable field error on a Deployment. It is
92
+ targeted, explicit, and safe — the only cost is the brief downtime while the object is recreated.
93
+
94
+ ---
95
+
47
96
  **`helmup`** — Context-safe Helm deployment tool. Installs or upgrades one or more services.
48
97
  ```bash
49
98
  helmup <service> [service...] # deploy one or more services
@@ -170,8 +219,98 @@ gcp-snapshots --project <id> [--age <days>] [--list|--delete]
170
219
 
171
220
  ### Kubernetes Utilities
172
221
 
173
- **`k8x`** — Opens an interactive shell (or specific CLI) on a running pod, with fuzzy
174
- finder support for pod selection. Requires `fzf`.
222
+ **`k8s`** — Unified Kubernetes toolkit with cluster-context safety via `overwhelm`. All
223
+ subcommands verify the active cluster context before acting to prevent cross-cluster
224
+ contamination.
225
+
226
+ ```bash
227
+ k8s fwd <target> # port-forward to a named cluster endpoint
228
+ k8s exe [target] # exec into a pod — shortcut or fuzzy match
229
+ k8s scale <up|dn> [deployments...] # scale deployments up or down
230
+ k8s roll [deployments...] # rolling restart of one or more deployments
231
+ k8s log [service] # stream pod logs with pino-pretty formatting
232
+ k8s cryo init # generate k8s-cryo.yaml from live cluster state
233
+ k8s cryo hibernate # scale all workloads to zero and drain node pools
234
+ k8s cryo wake # restore node pools and workload replica counts
235
+ ```
236
+
237
+ **`k8s fwd`** — Port-forwards a named cluster service to localhost. Handles `Ctrl+C` cleanly.
238
+ ```bash
239
+ k8s fwd elastic # localhost:9200 → elasticsearch8:9200
240
+ k8s fwd redis # localhost:6379 → redis-master:6379
241
+ k8s fwd valkey # localhost:6379 → valkey-master:6379
242
+ k8s fwd prom # localhost:9090 → prometheus-operated:9090
243
+ k8s fwd alert # localhost:9093 → alertmanager-operated:9093
244
+ k8s fwd grafana # localhost:3000 → prometheus-stack-grafana:3000
245
+ k8s fwd <target> --local <port> # override the local port
246
+ ```
247
+
248
+ **`k8s exe`** — Execs into a running pod. Named shortcuts resolve to the right namespace
249
+ and CLI automatically; any other string is used as a fuzzy filter against pods in the
250
+ default namespace.
251
+ ```bash
252
+ k8s exe # interactive pod picker (all namespaces)
253
+ k8s exe cnpg # bash on a cnpg-operands pod
254
+ k8s exe es8 # bash on an elasticsearch8 node
255
+ k8s exe grafana # bash on grafana (prometheus namespace)
256
+ k8s exe grafold # bash on legacy grafana (monitoring namespace)
257
+ k8s exe prometheus # bash on prometheus-operator pod
258
+ k8s exe psql # psql -U postgres on a CNPG node
259
+ k8s exe redis # redis-cli on redis master
260
+ k8s exe valkey # valkey-cli on valkey master
261
+ k8s exe <pattern> # fuzzy match against pod names in default namespace
262
+ ```
263
+
264
+ **`k8s scale`** — Scales one or more deployments in a namespace. Notifies Slack on each
265
+ result. Interactive picker if no deployment names are given.
266
+ ```bash
267
+ k8s scale up api-server message-processor # scale to 1 replica
268
+ k8s scale dn api-server # scale to 0
269
+ k8s scale up # interactive: pick from listed deployments
270
+ k8s scale dn -n monitoring grafana # scale in a specific namespace
271
+ ```
272
+
273
+ **`k8s roll`** — Rolling restart of one or more deployments (`kubectl rollout restart`).
274
+ Notifies Slack on each result. Interactive picker if no names given.
275
+ ```bash
276
+ k8s roll api-server authz-server
277
+ k8s roll # interactive picker
278
+ k8s roll -n monitoring grafana
279
+ ```
280
+
281
+ **`k8s log`** — Streams pod logs with pino-pretty formatting. Follows the stream until
282
+ `Ctrl+C`. Can tee raw output to a timestamped file.
283
+ ```bash
284
+ k8s log # interactive pod picker (all namespaces)
285
+ k8s log api-server # stream by app= label selector, default namespace
286
+ k8s log api-server -n staging # specify namespace
287
+ k8s log api-server --tee # also save raw logs to api-server-<timestamp>.log
288
+ k8s log api-server --tee my.log # save to a named file
289
+ k8s log api-server --no-pretty # raw output, skip pino-pretty
290
+ k8s log api-server --tail 200 # start with last 200 lines (default 50)
291
+ ```
292
+
293
+ **`k8s cryo`** — Cluster hibernation and wake tool. Uses `k8s-cryo.yaml` in the current
294
+ directory to define shutdown order, dependency phases, node pool sizing, and CNPG operator
295
+ awareness. Intended to be run from a cluster directory (e.g. `plat-k8s/tst-builder/`).
296
+ ```bash
297
+ k8s cryo init # scan live cluster and generate k8s-cryo.yaml
298
+ k8s cryo init --force # overwrite existing k8s-cryo.yaml
299
+ k8s cryo hibernate # stop all workloads and drain node pools
300
+ k8s cryo wake # restore nodes and scale workloads back up
301
+ k8s cryo hibernate ./path/to/k8s-cryo.yaml # explicit config path
302
+ ```
303
+ `k8s cryo init` scans the live cluster for deployments, StatefulSets, CNPG clusters and
304
+ poolers, node pools, and system namespaces (traefik, cert-manager, external-secrets, velero,
305
+ estafette, prometheus, monitoring). It queries live replica counts for variable-size
306
+ StatefulSets (timescale slave, valkey replicas, elasticsearch data) and emits a complete
307
+ `k8s-cryo.yaml`. Review the generated file — especially the Project phase member list and
308
+ any cluster-specific replica counts — before committing.
309
+
310
+ ---
311
+
312
+ **`k8x`** *(deprecated — use `k8s exe`)* — Opens an interactive shell (or specific CLI)
313
+ on a running pod, with fuzzy finder support for pod selection. Requires `fzf`.
175
314
  ```bash
176
315
  k8x # open bash on any pod (fuzzy select)
177
316
  k8x cnpg # bash on a cnpg cluster node
@@ -182,15 +321,16 @@ k8x redis # redis-cli on redis master
182
321
  k8x valkey # valkey-cli on valkey master
183
322
  ```
184
323
 
185
- **`k8scale`** — Scales Kubernetes deployments or performs rolling restarts.
324
+ **`k8scale`** *(deprecated use `k8s scale` and `k8s roll`)* — Scales Kubernetes
325
+ deployments or performs rolling restarts.
186
326
  ```bash
187
327
  k8scale up [services] # scale up to minReplicas
188
328
  k8scale dn [services] # scale down to zero
189
329
  k8scale roll [services] # rolling restart
190
330
  ```
191
331
 
192
- **`klog`** — Streams logs from a pod with pino-pretty formatting. Saves to a timestamped
193
- log file. Requires `fzf` for interactive selection.
332
+ **`klog`** *(deprecated use `k8s log`)* — Streams logs from a pod with pino-pretty
333
+ formatting. Saves to a timestamped log file. Requires `fzf` for interactive selection.
194
334
  ```bash
195
335
  klog # fuzzy-select pod and stream logs
196
336
  klog <app-name> # stream logs for named app
@@ -357,6 +497,9 @@ BUILD_TOOLS_DEBUG=1 docker-to-registry
357
497
 
358
498
  | Tool | Replacement |
359
499
  |------|-------------|
500
+ | `k8x` | `k8s exe` |
501
+ | `k8scale` | `k8s scale` / `k8s roll` |
502
+ | `klog` | `k8s log` |
360
503
  | `pkgck` | `repo-panopticon` |
361
504
  | `docker-to-registry-py` | `docker-to-registry` |
362
505
  | `chart-to-museum` | `push-my-chart` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leverege/build-tools",
3
- "version": "2.114.0-DEVOP-578.2",
3
+ "version": "2.114.1",
4
4
  "description": "A collection of build / support tools for Leverege developers",
5
5
  "main": "index.js",
6
6
  "repository": {
@@ -74,7 +74,7 @@
74
74
  },
75
75
  "author": "Leverege Devs",
76
76
  "engines": {
77
- "node": "^22.22 || >=24.15",
77
+ "node": "^22.22 || >=24",
78
78
  "npm": ">=11.10"
79
79
  },
80
80
  "docker": {
@@ -100,7 +100,7 @@
100
100
  "execa": "^10.0.1",
101
101
  "find-yarn-workspace-root": "^2.0.0",
102
102
  "glob": "^13.0.6",
103
- "googleapis": "^173.0.0",
103
+ "googleapis": "^174.0.1",
104
104
  "handlebars": "^4.7.9",
105
105
  "ignore": "^7.0.6",
106
106
  "inquirer": "^14.0.2",
@@ -0,0 +1,52 @@
1
+ #!/bin/bash
2
+
3
+ function createLlmPromptServerJwtSecret() {
4
+ # Create a placeholder k8s secret so llm-prompt-server starts cleanly on first install.
5
+ # ESO will then be wired up to manage the actual JWT credentials from GCP Secret Manager.
6
+ kubectl get secret llm-prompt-server-jwt --namespace default &> $DEVNULL
7
+ if [ $? -ne 0 ];
8
+ then
9
+ printf "\nCreating placeholder `color g llm-prompt-server-jwt` secret in default namespace\n"
10
+ kubectl create secret generic llm-prompt-server-jwt \
11
+ --namespace default \
12
+ --from-literal=config='{"jwtSub":"llm-prompt-server","jwtSecret":"bootstrap","jwtIssuer":"imagineService"}'
13
+ warnOnError $? "Failed to create llm-prompt-server-jwt placeholder secret"
14
+ else
15
+ printf "\n`color g llm-prompt-server-jwt` secret already exists - skipping\n"
16
+ fi
17
+
18
+ # Wire up the ExternalSecret only if ESO is enabled for this cluster.
19
+ # Set ESO_ENABLED=true in overwhelm.yaml to activate (default: false).
20
+ if [ "${ESO_ENABLED}" = "true" ];
21
+ then
22
+ showInstalling "llm-prompt-server JWT ExternalSecret"
23
+ cat<<LLM_JWT_SECRET | kubectl apply -f -
24
+ apiVersion: external-secrets.io/v1
25
+ kind: ExternalSecret
26
+ metadata:
27
+ name: llm-prompt-server-jwt
28
+ namespace: default
29
+ spec:
30
+ refreshInterval: 1h
31
+ secretStoreRef:
32
+ name: gcp-secret-manager
33
+ kind: ClusterSecretStore
34
+ target:
35
+ name: llm-prompt-server-jwt
36
+ creationPolicy: Merge
37
+ template:
38
+ engineVersion: v2
39
+ data:
40
+ config: '{"jwtSub":"llm-prompt-server","jwtSecret":"{{ .JWT_SERVICE_SECRET }}","jwtIssuer":"imagineService"}'
41
+ data:
42
+ - secretKey: JWT_SERVICE_SECRET
43
+ remoteRef:
44
+ key: JWT_SERVICE_SECRET
45
+ LLM_JWT_SECRET
46
+ else
47
+ printf "\n`color y '***WARNING:'` ESO_ENABLED is not set - skipping llm-prompt-server JWT ExternalSecret\n"
48
+ printf " Set ESO_ENABLED=true in overwhelm.yaml and re-run helmup llm-prompt-server to wire up the JWT secret.\n\n"
49
+ fi
50
+ }
51
+
52
+ createLlmPromptServerJwtSecret
package/src/helmup.sh CHANGED
@@ -20,7 +20,7 @@ function giveHelp() {
20
20
  `color g "--k8s-match"` Only proceed if k8s context is correctly set
21
21
  `color g "--dry-run"` Show what helm would do without applying changes
22
22
  `color g "--debug"` Enable verbose debug output
23
- `color g "--mutate"` Forces a deployment mutation
23
+ `color g "--mutate"` Delete the k8s deployment object before upgrading (causes downtime)
24
24
  `color g "--help"` Show this help message
25
25
 
26
26
  Invocation Modes:
@@ -106,7 +106,6 @@ PLATFORM=(
106
106
  db-curator
107
107
  emailer
108
108
  imagine
109
- llm-prompt-server
110
109
  message-processor
111
110
  messenger
112
111
  resource-server
@@ -121,6 +120,7 @@ AUXILIARY=(
121
120
  argocd
122
121
  fota-server
123
122
  geotile-server
123
+ llm-prompt-server
124
124
  overdose
125
125
  pgbouncer
126
126
  pubsub-pulse
@@ -128,6 +128,7 @@ AUXILIARY=(
128
128
  pusher
129
129
  reason
130
130
  transponder-dh
131
+ triton-inference-server
131
132
  vin-decoder-server
132
133
  vms-server
133
134
  )
@@ -655,23 +656,53 @@ DELETE_WAIT
655
656
 
656
657
  # plugins pass HELMUP as a single quoted string - split it into args without using eval
657
658
  [[ $# -eq 1 ]] && set -- $1
658
- "$@" --set "helmup.culprit=$culprit" --set "helmup.deployT=$deployT"
659
-
659
+
660
+ # Tee helm output to a temp file so we can inspect it on failure while still
661
+ # streaming it to the terminal in real-time. 2>&1 captures stderr (where helm
662
+ # writes UPGRADE FAILED messages) alongside stdout.
663
+ local HELM_LOG
664
+ HELM_LOG=$(mktemp)
665
+ "$@" --set "helmup.culprit=$culprit" --set "helmup.deployT=$deployT" 2>&1 | tee "$HELM_LOG"
666
+ local HELM_EXIT=${PIPESTATUS[0]}
667
+
660
668
  # usually the helm command will run without error but a common failure is caused
661
669
  # by the lack of use of our protonVPN profile which causes the i/o timeout from helm
662
- if [[ $? -ne 0 ]];
670
+ if [[ $HELM_EXIT -ne 0 ]];
663
671
  then
664
- cat<<HELMUP_FAIL
672
+ if grep -q "field is immutable" "$HELM_LOG";
673
+ then
674
+ cat<<IMMUTABLE_FIELD
675
+
676
+ $RED_ERROR Helm upgrade failed — an immutable field has changed.
677
+
678
+ `color y "This typically happens when a service chart has been updated to use the new Leverege
679
+ Base Chart structure and the Deployment's label selector has changed. Kubernetes does not
680
+ allow selector changes on an existing Deployment — the object must be deleted and recreated."`
681
+
682
+ `color g "Re-run with --mutate to let helmup delete and recreate the Deployment for you:"`
683
+
684
+ `color c "helmup $SERVICE --mutate"`
685
+
686
+ `color y "Note: --mutate causes brief downtime while the Deployment is recreated.
687
+ The service's LoadBalancer, static IP, and any PVCs are unaffected."`
688
+
689
+ IMMUTABLE_FIELD
690
+ else
691
+ cat<<HELMUP_FAIL
665
692
 
666
693
  $RED_ERROR helmup failed for => `color y "$SERVICE"`
667
694
 
668
695
  `color y "Please investigate the issue, and if it isn't obvious then check your VPN connectivity!"`
669
696
 
670
697
  HELMUP_FAIL
698
+ fi
671
699
 
700
+ rm -f "$HELM_LOG"
672
701
  exit 1
673
702
  fi
674
703
 
704
+ rm -f "$HELM_LOG"
705
+
675
706
  case "$INVOKED_AS" in
676
707
  "helmup"|"helmdn")
677
708
  sendToSlack "$*"