@leverege/build-tools 2.113.6 → 2.114.0-DEVOP-578.2
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 +0 -15
- package/package.json +15 -10
- package/src/bash-funcs +1 -1
- package/src/build-tools.mjs +3 -2
- package/src/chart-compass.yaml +6 -1
- package/src/helm-charts/api-server/values-local.yaml +1 -1
- package/src/helm-charts/cnpg-db-psql-stack/cluster.yaml.ovh +1 -1
- package/src/helm-charts/cnpg-db-tsdb-basic/cluster.yaml.ovh +1 -1
- package/src/helm-charts/cnpg-db-tsdb-dense/cluster.yaml.ovh +1 -1
- package/src/helm-charts/llm-prompt-server/values-local.yaml +5 -0
- package/src/helm-charts/prom-operator/helmup.plugin +3 -3
- package/src/helm-charts/prom-operator/prometheus-stack.yaml.ovh +1 -1
- package/src/helm-charts/triton-inference-server/values-local.yaml +28 -0
- package/src/helm-charts/vms-server/values-local.yaml +45 -0
- package/src/helmup.sh +5 -4
- package/src/k8s/commands/completion.mjs +100 -0
- package/src/k8s/commands/cryo.mjs +164 -0
- package/src/k8s/commands/exe.mjs +57 -0
- package/src/k8s/commands/fwd.mjs +35 -0
- package/src/k8s/commands/log.mjs +76 -0
- package/src/k8s/commands/roll.mjs +37 -0
- package/src/k8s/commands/scale.mjs +43 -0
- package/src/k8s/k8s.mjs +26 -0
- package/src/k8s/lib/gate.mjs +9 -0
- package/src/k8s/lib/kubectl.mjs +26 -0
- package/src/k8s/lib/select.mjs +18 -0
- package/src/overwhelm.mjs +1 -1
- package/src/repo-panopticon.mjs +16 -12
- package/src/service-man/config/Config.mjs +1 -1
- package/src/service-man/config/iam-roles/leveregeVertexEndpointUser.json +10 -0
- package/src/service-man/config/service-accounts/llm-prompt-server.json +6 -0
- package/src/service-man/config/service-accounts/triton-inference-server.json +5 -0
- package/src/service-man/config/service-accounts/vms-server.json +8 -0
- package/src/service-man/gcloud-k8s/ServiceAccountManager.mjs +9 -5
- package/src/artifacts/ArtifactPuller.mjs +0 -131
- package/src/artifacts/ArtifactPusher.mjs +0 -110
- package/src/artifacts/Structs.mjs +0 -98
- package/src/artifacts/Utils.mjs +0 -284
- package/src/artifacts/pull-artifact.mjs +0 -81
- package/src/artifacts/push-artifact.mjs +0 -80
- package/src/change-up.sh +0 -27
package/README.md
CHANGED
|
@@ -199,21 +199,6 @@ klog <app-name> <ns> # specify namespace
|
|
|
199
199
|
|
|
200
200
|
---
|
|
201
201
|
|
|
202
|
-
### Artifact Management
|
|
203
|
-
|
|
204
|
-
**`pull-artifact`** — Pulls generic artifacts from GCP Artifact Registry based on an
|
|
205
|
-
`artifacts.yaml` spec file.
|
|
206
|
-
```bash
|
|
207
|
-
pull-artifact [-f <spec-file>] [-n <name>] [-v <version>]
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
**`push-artifact`** — Pushes generic artifacts to GCP Artifact Registry.
|
|
211
|
-
```bash
|
|
212
|
-
push-artifact [-f <spec-file>] [-n <name>] [-v <version>]
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
---
|
|
216
|
-
|
|
217
202
|
### Package & Release Management
|
|
218
203
|
|
|
219
204
|
**`hook-and-release`** — Manages git pre-commit hooks and version consistency for npm
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leverege/build-tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.114.0-DEVOP-578.2",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
],
|
|
13
13
|
"scripts": {
|
|
14
14
|
"lint": "eslint",
|
|
15
|
-
"lint
|
|
15
|
+
"lint:fix": "eslint --fix",
|
|
16
16
|
"prepack": "npm exec prepack",
|
|
17
17
|
"prepare": "[ $SKIP_PREPARE ] && exit 0 || ./src/hook-and-release.mjs",
|
|
18
18
|
"test": "mocha -t 60000 --exit 'test/**/!(*e2e*).mjs'",
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"bin": {
|
|
22
22
|
"build-cnpg-image": "src/build-cnpg-image.mjs",
|
|
23
23
|
"build-tools": "src/build-tools.mjs",
|
|
24
|
-
"change-up": "src/change-up.sh",
|
|
25
24
|
"chart-compass": "src/chart-compass.mjs",
|
|
26
25
|
"chart-to-registry": "src/chart-to-registry.mjs",
|
|
27
26
|
"circle-orb-it": "src/circle-orb-it/circle-orb-it.mjs",
|
|
@@ -53,15 +52,14 @@
|
|
|
53
52
|
"k8scale": "src/k8scale.sh",
|
|
54
53
|
"k8x": "src/k8x.sh",
|
|
55
54
|
"klog": "src/klog.sh",
|
|
55
|
+
"k8s": "src/k8s/k8s.mjs",
|
|
56
56
|
"npm-link": "src/npm-link.sh",
|
|
57
57
|
"npm-unlink": "src/npm-unlink.sh",
|
|
58
58
|
"overwhelm": "src/overwhelm.mjs",
|
|
59
59
|
"pkglint": "src/pkglint.sh",
|
|
60
60
|
"prepack": "src/prepack.mjs",
|
|
61
61
|
"prune-git": "src/prune-git.sh",
|
|
62
|
-
"pull-artifact": "src/artifacts/pull-artifact.mjs",
|
|
63
62
|
"pull-git": "src/pull-git.sh",
|
|
64
|
-
"push-artifact": "src/artifacts/push-artifact.mjs",
|
|
65
63
|
"push-my-chart": "src/push-my-chart.mjs",
|
|
66
64
|
"refresh-npm-token": "src/refresh-npm-token.mjs",
|
|
67
65
|
"refresh-py-idx": "src/refresh-py-idx.sh",
|
|
@@ -70,6 +68,7 @@
|
|
|
70
68
|
"service-man": "src/service-man/service-man.mjs",
|
|
71
69
|
"snapshot-cnpg": "src/snapshot-cnpg.mjs",
|
|
72
70
|
"sync-chart-version": "src/sync-chart-version.mjs",
|
|
71
|
+
"throwup": "src/helmup.sh",
|
|
73
72
|
"upload-model": "src/upload-model.sh",
|
|
74
73
|
"yarn-build": "src/yarn-build.mjs"
|
|
75
74
|
},
|
|
@@ -83,16 +82,22 @@
|
|
|
83
82
|
"npm": "11.12.0"
|
|
84
83
|
},
|
|
85
84
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
85
|
+
"overrides": {
|
|
86
|
+
"diff": "^8.0.3",
|
|
87
|
+
"mocha": {
|
|
88
|
+
"serialize-javascript": "^7.0.3"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
86
91
|
"dependencies": {
|
|
87
92
|
"@kubernetes/client-node": "^1.4.0",
|
|
88
93
|
"@leverege/jsdoc-template": "^1.0.1",
|
|
89
|
-
"chalk": "^
|
|
94
|
+
"chalk": "^6.0.0",
|
|
90
95
|
"command-line-args": "^6.0.2",
|
|
91
96
|
"command-line-usage": "^7.0.4",
|
|
92
97
|
"commander": "^15.0.0",
|
|
93
98
|
"deepmerge": "^4.3.1",
|
|
94
99
|
"enquirer": "^2.4.1",
|
|
95
|
-
"execa": "^
|
|
100
|
+
"execa": "^10.0.1",
|
|
96
101
|
"find-yarn-workspace-root": "^2.0.0",
|
|
97
102
|
"glob": "^13.0.6",
|
|
98
103
|
"googleapis": "^173.0.0",
|
|
@@ -113,7 +118,7 @@
|
|
|
113
118
|
"sloc": "^0.3.2",
|
|
114
119
|
"superstruct": "^2.0.2",
|
|
115
120
|
"tmp": "^0.2.7",
|
|
116
|
-
"toml": "^
|
|
121
|
+
"toml": "^5.0.0",
|
|
117
122
|
"yaml": "^2.9.0",
|
|
118
123
|
"zx": "^8.8.5"
|
|
119
124
|
},
|
|
@@ -121,8 +126,8 @@
|
|
|
121
126
|
"@leverege/eslint-config-leverege": "^9",
|
|
122
127
|
"chai": "^6.2.2",
|
|
123
128
|
"eslint": "^9",
|
|
124
|
-
"mocha": "^11.
|
|
125
|
-
"npm": "^
|
|
129
|
+
"mocha": "^11.8.0",
|
|
130
|
+
"npm": "^12.0.2"
|
|
126
131
|
},
|
|
127
132
|
"allowScripts": {
|
|
128
133
|
"unrs-resolver@1.12.2": true
|
package/src/bash-funcs
CHANGED
|
@@ -298,7 +298,7 @@ function ifPluggedIn() {
|
|
|
298
298
|
local PLUGIN=
|
|
299
299
|
HELMUP="helm upgrade --install $SERVICE $CHARTLOC -f $SERVICE/values.yaml $CHARTVER $HELM_WHAT"
|
|
300
300
|
case "$INVOKED_AS" in
|
|
301
|
-
"helmup"|"helmwhat"|"helmcycle"|"helminit" )
|
|
301
|
+
"helmup"|"helmwhat"|"helmcycle"|"helminit"|"throwup" )
|
|
302
302
|
PLUGIN="$1/helmup.plugin"
|
|
303
303
|
;;
|
|
304
304
|
"helmdn" )
|
package/src/build-tools.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import chalk from 'chalk'
|
|
|
9
9
|
import cliArgs from 'command-line-args'
|
|
10
10
|
import cliHelp from 'command-line-usage'
|
|
11
11
|
import npmRegistryFetch from 'npm-registry-fetch'
|
|
12
|
-
import { lt as semverLt } from 'semver'
|
|
12
|
+
import { lt as semverLt, prerelease } from 'semver'
|
|
13
13
|
|
|
14
14
|
// because __dirname goes away with ESM
|
|
15
15
|
const getFilename = () => {
|
|
@@ -124,7 +124,8 @@ const checkForLatest = async ( pkg = '@leverege/build-tools' ) => {
|
|
|
124
124
|
const list = await npmRegistryFetch.json(
|
|
125
125
|
pkg, { '//registry.npmjs.org/:_authToken' : getToken() }
|
|
126
126
|
)
|
|
127
|
-
|
|
127
|
+
const latest = list['dist-tags'].latest
|
|
128
|
+
return prerelease( latest ) ? undefined : latest
|
|
128
129
|
} catch ( err ) {
|
|
129
130
|
console.log( err )
|
|
130
131
|
}
|
package/src/chart-compass.yaml
CHANGED
|
@@ -15,7 +15,7 @@ config:
|
|
|
15
15
|
MODEL_SQL_HOST: "cnpg-db-psql-stack-pool-rw.cnpg-operands"
|
|
16
16
|
PG_MODELS_HOST: "cnpg-db-psql-stack-pool-rw.cnpg-operands"
|
|
17
17
|
# timescale
|
|
18
|
-
PG_HOST: "cnpg-db-tsdb-basic-
|
|
18
|
+
PG_HOST: "cnpg-db-tsdb-basic-r.cnpg-operands"
|
|
19
19
|
# imagine db is legacy - newer setups use timescale as the db name
|
|
20
20
|
PG_DATABASE: "timescale"
|
|
21
21
|
PG_DENSE_HISTORY_DATABASE: "timescale"
|
|
@@ -8,14 +8,14 @@ showInstalling "The Prometheus Operator (kube-prometheus-stack)"
|
|
|
8
8
|
|
|
9
9
|
# https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack
|
|
10
10
|
OCI_CHART="oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack"
|
|
11
|
-
[ -z "$PROMETHEUS_STACK_CHART_VERSION" ] && PROMETHEUS_STACK_CHART_VERSION="
|
|
11
|
+
[ -z "$PROMETHEUS_STACK_CHART_VERSION" ] && PROMETHEUS_STACK_CHART_VERSION="87.21.0"
|
|
12
12
|
helm upgrade $NS --install prometheus-stack $OCI_CHART \
|
|
13
13
|
--values prom-operator/prometheus-stack.yaml \
|
|
14
14
|
--version $PROMETHEUS_STACK_CHART_VERSION $HELM_WHAT
|
|
15
15
|
|
|
16
16
|
# https://artifacthub.io/packages/helm/prometheus-community/prometheus-elasticsearch-exporter
|
|
17
17
|
showInstalling "The Elasticsearch Exporter (prom-operator ES metrics exports)"
|
|
18
|
-
[ -z "$ELASTICSEARCH_EXPORTER_CHART_VERSION" ] && ELASTICSEARCH_EXPORTER_CHART_VERSION="7.
|
|
18
|
+
[ -z "$ELASTICSEARCH_EXPORTER_CHART_VERSION" ] && ELASTICSEARCH_EXPORTER_CHART_VERSION="7.4.0"
|
|
19
19
|
OCI_CHART="oci://ghcr.io/prometheus-community/charts/prometheus-elasticsearch-exporter"
|
|
20
20
|
helm upgrade $NS --install elasticsearch8-exporter $OCI_CHART \
|
|
21
21
|
--values prom-operator/elasticsearch-exporter.yaml \
|
|
@@ -23,7 +23,7 @@ helm upgrade $NS --install elasticsearch8-exporter $OCI_CHART \
|
|
|
23
23
|
|
|
24
24
|
# https://artifacthub.io/packages/helm/prometheus-community/prometheus-stackdriver-exporter
|
|
25
25
|
showInstalling "The Stackdriver Exporter (prom-operator GKE metrics exports)"
|
|
26
|
-
[ -z "$STACKDRIVER_EXPORTER_CHART_VERSION" ] && STACKDRIVER_EXPORTER_CHART_VERSION="4.
|
|
26
|
+
[ -z "$STACKDRIVER_EXPORTER_CHART_VERSION" ] && STACKDRIVER_EXPORTER_CHART_VERSION="4.13.0"
|
|
27
27
|
OCI_CHART="oci://ghcr.io/prometheus-community/charts/prometheus-stackdriver-exporter"
|
|
28
28
|
helm upgrade $NS --install stackdriver-exporter $OCI_CHART \
|
|
29
29
|
--values prom-operator/stackdriver-exporter.yaml \
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
image:
|
|
2
|
+
tag:
|
|
3
|
+
|
|
4
|
+
modelInit:
|
|
5
|
+
sources:
|
|
6
|
+
triton: "gs://${PROJECT_ID}-triton-model-repository/models"
|
|
7
|
+
targetDir: "/models"
|
|
8
|
+
|
|
9
|
+
# container args may be tuned as necessary - see the repo README for more info
|
|
10
|
+
#
|
|
11
|
+
# https://bitbucket.org/leverege/triton-inference-server/src/development/
|
|
12
|
+
|
|
13
|
+
# example of how to add objects
|
|
14
|
+
#extraObjects:
|
|
15
|
+
# - apiVersion: traefik.io/v1alpha1
|
|
16
|
+
# kind: IngressRoute
|
|
17
|
+
# metadata:
|
|
18
|
+
# name: ${PROJECT_NAME}-extra-api-${HOST}-com
|
|
19
|
+
# namespace: default
|
|
20
|
+
# spec:
|
|
21
|
+
# entryPoints:
|
|
22
|
+
# - websecure
|
|
23
|
+
# routes:
|
|
24
|
+
# - match: Host(`${PROJECT_NAME}-test-api.${HOST}.com`)
|
|
25
|
+
# kind: Rule
|
|
26
|
+
# services:
|
|
27
|
+
# - name: triton-inference-server
|
|
28
|
+
# port: 8001
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
image:
|
|
2
|
+
tag:
|
|
3
|
+
|
|
4
|
+
config:
|
|
5
|
+
LOG_LEVEL: "WARNING"
|
|
6
|
+
GCP_PROJECT_ID: "${PROJECT_ID}" # needed for python secrets manager?
|
|
7
|
+
GCP_SECRET_MANAGER_NAMESPACE: "VMS_SERVER"
|
|
8
|
+
IMAGINE_API_HOST: "http://api-server:8181"
|
|
9
|
+
IMAGINE_API_PROJECT_ID: "set-in-values-local"
|
|
10
|
+
IMAGINE_API_SYSTEM_ID: "set-in-values-local"
|
|
11
|
+
IMAGINE_API_KEY: "set-in-values-local"
|
|
12
|
+
IMAGINE_API_AUDIENCE: "${PROJECT_ID}-aud"
|
|
13
|
+
IMAGINE_SERVICE_SYNC_ENABLED: "true"
|
|
14
|
+
IMAGINE_CAMERA_BLUEPRINT_ID: "set-in-values-local"
|
|
15
|
+
VMS_SERVER_HOST: "http://vms-server:8080"
|
|
16
|
+
VIDEO_PLAYBACK_SERVICE_VERSION: "20"
|
|
17
|
+
VIDEO_MANAGEMENT_SERVICE_VERSION: "2"
|
|
18
|
+
USE_SHARED_METRICS: "true"
|
|
19
|
+
VIDEO_SEGMENTS_UPLOAD_TIMEOUT: "60"
|
|
20
|
+
PLAYLIST_OPEN_ENDED_CACHE_ENABLED: "true"
|
|
21
|
+
SEGMENT_RECONCILER_ENABLED: "true"
|
|
22
|
+
SEGMENT_RECONCILER_INTERVAL_SECONDS: "300"
|
|
23
|
+
SEGMENT_RECONCILER_MIN_AGE_SECONDS: "300"
|
|
24
|
+
SEGMENT_RECONCILER_LOOKBACK_HOURS: "24"
|
|
25
|
+
|
|
26
|
+
# run on the preemptible node pool (base chart adds the target-env=preemptible
|
|
27
|
+
# nodeAffinity, toleration and safe-to-evict annotation)
|
|
28
|
+
service:
|
|
29
|
+
preemptible: true
|
|
30
|
+
|
|
31
|
+
serviceMonitor:
|
|
32
|
+
enabled: true
|
|
33
|
+
interval: "60s"
|
|
34
|
+
namespace: "default"
|
|
35
|
+
|
|
36
|
+
resources:
|
|
37
|
+
requests:
|
|
38
|
+
cpu: 500m
|
|
39
|
+
memory: 512Mi
|
|
40
|
+
|
|
41
|
+
autoscaling:
|
|
42
|
+
enabled: true
|
|
43
|
+
minReplicas: 2
|
|
44
|
+
maxReplicas: 16
|
|
45
|
+
averageCPU: 75
|
package/src/helmup.sh
CHANGED
|
@@ -52,9 +52,6 @@ for arg in "$@"; do
|
|
|
52
52
|
--mutate)
|
|
53
53
|
MUTATE_DEPLOYMENT=1
|
|
54
54
|
;;
|
|
55
|
-
--conflicts)
|
|
56
|
-
export FORCE_CONFLICTS=1
|
|
57
|
-
;;
|
|
58
55
|
--help|-h)
|
|
59
56
|
giveHelp && exit 0
|
|
60
57
|
;;
|
|
@@ -100,6 +97,8 @@ gcloud auth application-default print-access-token | helm registry login \
|
|
|
100
97
|
refresh-npm-token
|
|
101
98
|
exitOnError $? "refresh-npm-token failed - the rc files were not refreshed"
|
|
102
99
|
|
|
100
|
+
HELM_EMOJI=$( [ "$INVOKED_AS" == "throwup" ] && echo "🤮" || echo "⎈" )
|
|
101
|
+
|
|
103
102
|
# removed geotile-server from standard platform 3/20/22 - low usage
|
|
104
103
|
PLATFORM=(
|
|
105
104
|
authz-server
|
|
@@ -107,6 +106,7 @@ PLATFORM=(
|
|
|
107
106
|
db-curator
|
|
108
107
|
emailer
|
|
109
108
|
imagine
|
|
109
|
+
llm-prompt-server
|
|
110
110
|
message-processor
|
|
111
111
|
messenger
|
|
112
112
|
resource-server
|
|
@@ -129,6 +129,7 @@ AUXILIARY=(
|
|
|
129
129
|
reason
|
|
130
130
|
transponder-dh
|
|
131
131
|
vin-decoder-server
|
|
132
|
+
vms-server
|
|
132
133
|
)
|
|
133
134
|
|
|
134
135
|
# There's a chicken / egg situation that arises with new clusters in that
|
|
@@ -629,7 +630,7 @@ HELMUP_BANNER
|
|
|
629
630
|
function doHelmup() {
|
|
630
631
|
local culprit="$(gcloud config get account)"
|
|
631
632
|
local deployT="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
|
|
632
|
-
local doing="Helming => `color g \"$*\"`"
|
|
633
|
+
local doing="Helming => $HELM_EMOJI `color g \"$*\"` $HELM_EMOJI"
|
|
633
634
|
local from=
|
|
634
635
|
[ ! -z "$FROM_VERSIONS" ] && from="`color y \"<= from Versions.json\"`"
|
|
635
636
|
printf "$doing $from\n"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
|
|
3
|
+
import { errorExit } from '../../Utils.mjs'
|
|
4
|
+
|
|
5
|
+
const SHELLS = [ 'bash', 'zsh', 'fish' ]
|
|
6
|
+
|
|
7
|
+
const BASH = `\
|
|
8
|
+
_k8s_completions() {
|
|
9
|
+
local cur prev words
|
|
10
|
+
COMPREPLY=()
|
|
11
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
12
|
+
words=("\${COMP_WORDS[@]}")
|
|
13
|
+
|
|
14
|
+
case "\${words[1]}" in
|
|
15
|
+
fwd)
|
|
16
|
+
COMPREPLY=($(compgen -W "elastic redis valkey prom alert grafana" -- "$cur"))
|
|
17
|
+
return ;;
|
|
18
|
+
exe)
|
|
19
|
+
COMPREPLY=($(compgen -W "cnpg es8 grafana grafold prom-operator psql redis valkey" -- "$cur"))
|
|
20
|
+
return ;;
|
|
21
|
+
scale)
|
|
22
|
+
[ "\${#words[@]}" -eq 3 ] && COMPREPLY=($(compgen -W "up dn" -- "$cur"))
|
|
23
|
+
return ;;
|
|
24
|
+
log)
|
|
25
|
+
COMPREPLY=($(compgen -W "--namespace --container --tail --tee --no-pretty" -- "$cur"))
|
|
26
|
+
return ;;
|
|
27
|
+
completion)
|
|
28
|
+
COMPREPLY=($(compgen -W "bash zsh fish" -- "$cur"))
|
|
29
|
+
return ;;
|
|
30
|
+
*)
|
|
31
|
+
COMPREPLY=($(compgen -W "fwd exe scale roll log completion" -- "$cur"))
|
|
32
|
+
;;
|
|
33
|
+
esac
|
|
34
|
+
}
|
|
35
|
+
complete -F _k8s_completions k8s
|
|
36
|
+
`
|
|
37
|
+
|
|
38
|
+
const ZSH = `\
|
|
39
|
+
#compdef k8s
|
|
40
|
+
|
|
41
|
+
_k8s() {
|
|
42
|
+
local state
|
|
43
|
+
_arguments '1: :->cmd' '*: :->args'
|
|
44
|
+
|
|
45
|
+
case $state in
|
|
46
|
+
cmd)
|
|
47
|
+
local -a commands=(
|
|
48
|
+
'fwd:Port-forward to a cluster endpoint'
|
|
49
|
+
'exe:Exec into a pod interactively'
|
|
50
|
+
'scale:Scale a deployment up or down'
|
|
51
|
+
'roll:Rolling restart of deployments'
|
|
52
|
+
'log:Stream logs with pino-pretty'
|
|
53
|
+
'completion:Emit shell completion script'
|
|
54
|
+
)
|
|
55
|
+
_describe 'command' commands ;;
|
|
56
|
+
args)
|
|
57
|
+
case $words[2] in
|
|
58
|
+
fwd) _values target elastic redis valkey prom alert grafana ;;
|
|
59
|
+
exe) _values target cnpg es8 grafana grafold prom-operator psql redis valkey ;;
|
|
60
|
+
scale) [ $CURRENT -eq 3 ] && _values direction up dn ;;
|
|
61
|
+
completion) _values shell bash zsh fish ;;
|
|
62
|
+
esac ;;
|
|
63
|
+
esac
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_k8s
|
|
67
|
+
`
|
|
68
|
+
|
|
69
|
+
const FISH = `\
|
|
70
|
+
set -l cmds fwd exe scale roll log completion
|
|
71
|
+
complete -c k8s -f
|
|
72
|
+
complete -c k8s -n "not __fish_seen_subcommand_from $cmds" -a fwd -d 'Port-forward to a cluster endpoint'
|
|
73
|
+
complete -c k8s -n "not __fish_seen_subcommand_from $cmds" -a exe -d 'Exec into a pod interactively'
|
|
74
|
+
complete -c k8s -n "not __fish_seen_subcommand_from $cmds" -a scale -d 'Scale a deployment up or down'
|
|
75
|
+
complete -c k8s -n "not __fish_seen_subcommand_from $cmds" -a roll -d 'Rolling restart of deployments'
|
|
76
|
+
complete -c k8s -n "not __fish_seen_subcommand_from $cmds" -a log -d 'Stream logs with pino-pretty'
|
|
77
|
+
complete -c k8s -n "not __fish_seen_subcommand_from $cmds" -a completion -d 'Emit shell completion script'
|
|
78
|
+
|
|
79
|
+
complete -c k8s -n "__fish_seen_subcommand_from fwd" -a "elastic redis valkey prom alert grafana"
|
|
80
|
+
complete -c k8s -n "__fish_seen_subcommand_from exe" -a "cnpg es8 grafana grafold prom-operator psql redis valkey"
|
|
81
|
+
complete -c k8s -n "__fish_seen_subcommand_from scale; and not __fish_seen_subcommand_from up dn" -a "up dn"
|
|
82
|
+
complete -c k8s -n "__fish_seen_subcommand_from completion" -a "bash zsh fish"
|
|
83
|
+
`
|
|
84
|
+
|
|
85
|
+
export const completionCommand = new Command( 'completion' )
|
|
86
|
+
.description( 'Emit a shell completion script' )
|
|
87
|
+
.argument( '<shell>', `shell: ${SHELLS.join( ', ' )}` )
|
|
88
|
+
.addHelpText( 'after', `
|
|
89
|
+
Examples:
|
|
90
|
+
eval "$(k8s completion bash)" # add to ~/.bashrc
|
|
91
|
+
eval "$(k8s completion zsh)" # add to ~/.zshrc
|
|
92
|
+
k8s completion fish | source # fish interactive session
|
|
93
|
+
` )
|
|
94
|
+
.action( ( shell ) => {
|
|
95
|
+
const scripts = { bash: BASH, zsh: ZSH, fish: FISH }
|
|
96
|
+
if ( !scripts[shell] ) {
|
|
97
|
+
errorExit( `Unknown shell: ${shell}\nValid: ${SHELLS.join( ', ' )}` )
|
|
98
|
+
}
|
|
99
|
+
process.stdout.write( scripts[shell] )
|
|
100
|
+
} )
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { $ } from 'zx'
|
|
4
|
+
|
|
5
|
+
import { log, errorExit, sleep, warning, proceed, parseJsonFile } from '../../Utils.mjs'
|
|
6
|
+
import { overwhelmGate } from '../lib/gate.mjs'
|
|
7
|
+
|
|
8
|
+
$.verbose = false
|
|
9
|
+
|
|
10
|
+
// ── Config ────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
const loadConfig = async ( configPath ) => {
|
|
13
|
+
const config = await parseJsonFile( configPath )
|
|
14
|
+
if ( !config ) errorExit( `Config file not found: ${configPath}` )
|
|
15
|
+
|
|
16
|
+
const { cluster, deploymentPhases, nodePools } = config
|
|
17
|
+
if ( !cluster?.project || !cluster?.name || !cluster?.region ) {
|
|
18
|
+
errorExit( 'Config must include cluster.project, cluster.name, and cluster.region' )
|
|
19
|
+
}
|
|
20
|
+
if ( !deploymentPhases || !nodePools ) {
|
|
21
|
+
errorExit( 'Config must include deploymentPhases and nodePools' )
|
|
22
|
+
}
|
|
23
|
+
return config
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// ── Display ───────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const banner = ( text ) => {
|
|
29
|
+
const bar = '─'.repeat( Math.max( 0, 56 - text.length ) )
|
|
30
|
+
log( chalk.cyan.bold( `\n── ${text} ${bar}` ) )
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── kubectl ───────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
const scaleDeployments = async ( names, replicas, namespace ) =>
|
|
36
|
+
$`kubectl scale deployment ${names} --replicas=${replicas} -n ${namespace}`
|
|
37
|
+
|
|
38
|
+
const scaleStatefulSets = async ( names, replicas, namespace ) =>
|
|
39
|
+
$`kubectl scale statefulset ${names} --replicas=${replicas} -n ${namespace}`
|
|
40
|
+
|
|
41
|
+
const waitForPodsGone = async ( namespace, timeoutSecs = 300 ) => {
|
|
42
|
+
log( `\nWaiting for pods to terminate (up to ${timeoutSecs}s)...` )
|
|
43
|
+
try {
|
|
44
|
+
await $`kubectl wait pod --all -n ${namespace} --for=delete --timeout=${timeoutSecs}s`
|
|
45
|
+
} catch {
|
|
46
|
+
warning( `Some pods still terminating after ${timeoutSecs}s — proceeding.` )
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const waitForNodesReady = async ( timeoutSecs = 600 ) => {
|
|
51
|
+
log( `\nWaiting for nodes to become Ready (up to ${timeoutSecs}s)...` )
|
|
52
|
+
await $`kubectl wait node --all --for=condition=Ready --timeout=${timeoutSecs}s`
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── gcloud ────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
const updateAutoscalerMin = async ( pool, cluster, min ) => {
|
|
58
|
+
log( ` ${pool.name}: autoscaler min → ${min}` )
|
|
59
|
+
const { name, project, region } = cluster
|
|
60
|
+
await $`gcloud container node-pools update ${pool.name} --cluster=${name} --project=${project} --region=${region} --enable-autoscaling --min-nodes=${min} --max-nodes=${pool.autoscaler.max} --quiet`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const resizePool = async ( pool, cluster, size ) => {
|
|
64
|
+
log( ` ${pool.name} → ${size} node(s)` )
|
|
65
|
+
const { name, project, region } = cluster
|
|
66
|
+
await $`gcloud container clusters resize ${name} --node-pool=${pool.name} --num-nodes=${size} --project=${project} --region=${region} --quiet`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Hibernate ─────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
const runHibernate = async ( configPath ) => {
|
|
72
|
+
const config = await loadConfig( configPath )
|
|
73
|
+
const {
|
|
74
|
+
cluster,
|
|
75
|
+
namespace = 'default',
|
|
76
|
+
phasePauseMs = 10_000,
|
|
77
|
+
deploymentPhases,
|
|
78
|
+
statefulsetPhases = [],
|
|
79
|
+
nodePools,
|
|
80
|
+
} = config
|
|
81
|
+
|
|
82
|
+
await overwhelmGate()
|
|
83
|
+
await proceed( `Hibernate ${chalk.yellow.bold( cluster.name )}? All workloads scale to 0 and node pools drain.` )
|
|
84
|
+
|
|
85
|
+
for ( const group of deploymentPhases ) {
|
|
86
|
+
banner( `Deployments: ${group.name}` )
|
|
87
|
+
await scaleDeployments( group.members, 0, namespace )
|
|
88
|
+
await sleep( group.pauseMs ?? phasePauseMs )
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if ( statefulsetPhases.length ) {
|
|
92
|
+
for ( const group of statefulsetPhases ) {
|
|
93
|
+
banner( `StatefulSets: ${group.name}` )
|
|
94
|
+
await scaleStatefulSets( group.members, 0, namespace )
|
|
95
|
+
await sleep( group.pauseMs ?? phasePauseMs )
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
// no ordered phases defined yet — scale all StatefulSets together
|
|
99
|
+
banner( 'StatefulSets' )
|
|
100
|
+
await $`kubectl scale statefulset --all -n ${namespace} --replicas=0`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
await waitForPodsGone( namespace )
|
|
104
|
+
|
|
105
|
+
const poolsWithAutoscaler = nodePools.filter( p => p.autoscaler )
|
|
106
|
+
if ( poolsWithAutoscaler.length ) {
|
|
107
|
+
banner( 'Clearing autoscaler minimums' )
|
|
108
|
+
for ( const pool of poolsWithAutoscaler ) {
|
|
109
|
+
await updateAutoscalerMin( pool, cluster, 0 )
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
banner( 'Draining node pools' )
|
|
114
|
+
for ( const pool of nodePools ) {
|
|
115
|
+
await resizePool( pool, cluster, 0 )
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
log( chalk.green( '\nCluster hibernated. Control plane (~$0.10/hr) continues running.' ) )
|
|
119
|
+
log( `To wake: ${chalk.cyan( `k8s wake ${configPath}` )}` )
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Wake ──────────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
const runWake = async ( configPath ) => {
|
|
125
|
+
const config = await loadConfig( configPath )
|
|
126
|
+
const { cluster, nodePools } = config
|
|
127
|
+
|
|
128
|
+
await overwhelmGate()
|
|
129
|
+
|
|
130
|
+
// Restore autoscaler minimums before resizing so the autoscaler
|
|
131
|
+
// doesn't immediately scale back down the nodes we're bringing up.
|
|
132
|
+
const poolsWithAutoscaler = nodePools.filter( p => p.autoscaler )
|
|
133
|
+
if ( poolsWithAutoscaler.length ) {
|
|
134
|
+
banner( 'Restoring autoscaler minimums' )
|
|
135
|
+
for ( const pool of poolsWithAutoscaler ) {
|
|
136
|
+
await updateAutoscalerMin( pool, cluster, pool.autoscaler.min )
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
banner( 'Restoring node pools' )
|
|
141
|
+
for ( const pool of nodePools ) {
|
|
142
|
+
if ( pool.restoreSize > 0 ) {
|
|
143
|
+
await resizePool( pool, cluster, pool.restoreSize )
|
|
144
|
+
} else {
|
|
145
|
+
log( ` ${pool.name} → skipped (autoscaler brings up on demand)` )
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await waitForNodesReady()
|
|
150
|
+
|
|
151
|
+
log( chalk.green( '\nNodes ready. Run helmup to restore all services.' ) )
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ── Commands ──────────────────────────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
export const hibernateCommand = new Command( 'hibernate' )
|
|
157
|
+
.description( 'Scale all workloads to zero and drain all node pools' )
|
|
158
|
+
.argument( '[config]', 'path to cluster-config.json', './cluster-config.json' )
|
|
159
|
+
.action( async ( config ) => runHibernate( config ).catch( errorExit ) )
|
|
160
|
+
|
|
161
|
+
export const wakeCommand = new Command( 'wake' )
|
|
162
|
+
.description( 'Restore node pools from hibernation (then run helmup)' )
|
|
163
|
+
.argument( '[config]', 'path to cluster-config.json', './cluster-config.json' )
|
|
164
|
+
.action( async ( config ) => runWake( config ).catch( errorExit ) )
|