@leverege/build-tools 2.113.5 → 2.114.0-DEVOP-578.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 +0 -15
- package/package.json +13 -9
- package/src/build-tools.mjs +3 -2
- package/src/chart-compass.yaml +6 -1
- package/src/circle-orb-it/circle-npm.yml +8 -0
- 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/helmup.sh +0 -3
- package/src/k8s/commands/completion.mjs +100 -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 +23 -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/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.1",
|
|
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",
|
|
@@ -83,16 +81,22 @@
|
|
|
83
81
|
"npm": "11.12.0"
|
|
84
82
|
},
|
|
85
83
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
84
|
+
"overrides": {
|
|
85
|
+
"diff": "^8.0.3",
|
|
86
|
+
"mocha": {
|
|
87
|
+
"serialize-javascript": "^7.0.3"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
86
90
|
"dependencies": {
|
|
87
91
|
"@kubernetes/client-node": "^1.4.0",
|
|
88
92
|
"@leverege/jsdoc-template": "^1.0.1",
|
|
89
|
-
"chalk": "^
|
|
93
|
+
"chalk": "^6.0.0",
|
|
90
94
|
"command-line-args": "^6.0.2",
|
|
91
95
|
"command-line-usage": "^7.0.4",
|
|
92
96
|
"commander": "^15.0.0",
|
|
93
97
|
"deepmerge": "^4.3.1",
|
|
94
98
|
"enquirer": "^2.4.1",
|
|
95
|
-
"execa": "^
|
|
99
|
+
"execa": "^10.0.0",
|
|
96
100
|
"find-yarn-workspace-root": "^2.0.0",
|
|
97
101
|
"glob": "^13.0.6",
|
|
98
102
|
"googleapis": "^173.0.0",
|
|
@@ -113,7 +117,7 @@
|
|
|
113
117
|
"sloc": "^0.3.2",
|
|
114
118
|
"superstruct": "^2.0.2",
|
|
115
119
|
"tmp": "^0.2.7",
|
|
116
|
-
"toml": "^
|
|
120
|
+
"toml": "^5.0.0",
|
|
117
121
|
"yaml": "^2.9.0",
|
|
118
122
|
"zx": "^8.8.5"
|
|
119
123
|
},
|
|
@@ -122,7 +126,7 @@
|
|
|
122
126
|
"chai": "^6.2.2",
|
|
123
127
|
"eslint": "^9",
|
|
124
128
|
"mocha": "^11.7.6",
|
|
125
|
-
"npm": "^
|
|
129
|
+
"npm": "^12.0.1"
|
|
126
130
|
},
|
|
127
131
|
"allowScripts": {
|
|
128
132
|
"unrs-resolver@1.12.2": true
|
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
|
@@ -6,8 +6,12 @@ orbs:
|
|
|
6
6
|
workflows:
|
|
7
7
|
build-and-verify:
|
|
8
8
|
jobs:
|
|
9
|
+
- leverege/install-dependencies:
|
|
10
|
+
context: npm
|
|
9
11
|
- leverege/code-analysis:
|
|
10
12
|
context: npm
|
|
13
|
+
requires:
|
|
14
|
+
- leverege/install-dependencies
|
|
11
15
|
- leverege/code-coverage:
|
|
12
16
|
context:
|
|
13
17
|
- npm
|
|
@@ -15,8 +19,12 @@ workflows:
|
|
|
15
19
|
coverage-threshold: 70 # percentage threshold for functions, lines, and statements
|
|
16
20
|
enable-redis: false # set to true if tests require a live Redis connection
|
|
17
21
|
cache-config: '{"type":"inMemory"}' # sets CACHE_CONFIG env var - options: inMemory, redis
|
|
22
|
+
requires:
|
|
23
|
+
- leverege/install-dependencies
|
|
18
24
|
- leverege/generate-docs:
|
|
19
25
|
context: npm
|
|
26
|
+
requires:
|
|
27
|
+
- leverege/install-dependencies
|
|
20
28
|
- leverege/mirror-to-github:
|
|
21
29
|
context: github-mirror
|
|
22
30
|
# github-team: platform # available teams: platform, utilities, pitcrew, recovr
|
|
@@ -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
|
package/src/helmup.sh
CHANGED
|
@@ -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,57 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { $ } from 'zx'
|
|
4
|
+
|
|
5
|
+
import { log, errorExit } from '../../Utils.mjs'
|
|
6
|
+
import { overwhelmGate } from '../lib/gate.mjs'
|
|
7
|
+
import { listPods } from '../lib/kubectl.mjs'
|
|
8
|
+
import { selectPod } from '../lib/select.mjs'
|
|
9
|
+
|
|
10
|
+
const TARGETS = {
|
|
11
|
+
cnpg: { ns: 'cnpg-operands', query: null, cmd: [ 'bash' ] },
|
|
12
|
+
es8: { ns: 'elastic', query: null, cmd: [ 'bash' ] },
|
|
13
|
+
elastic8: { ns: 'elastic', query: null, cmd: [ 'bash' ] },
|
|
14
|
+
grafana: { ns: 'prometheus', query: 'grafana', cmd: [ 'bash' ] },
|
|
15
|
+
grafold: { ns: 'monitoring', query: 'grafana', cmd: [ 'bash' ] },
|
|
16
|
+
'prom-operator': { ns: 'prometheus', query: null, cmd: [ 'bash' ] },
|
|
17
|
+
prometheus: { ns: 'prometheus', query: null, cmd: [ 'bash' ] },
|
|
18
|
+
psql: { ns: 'cnpg-operands', query: 'postgresql', cmd: [ 'psql', '-U', 'postgres' ] },
|
|
19
|
+
pgsql: { ns: 'cnpg-operands', query: 'postgresql', cmd: [ 'psql', '-U', 'postgres' ] },
|
|
20
|
+
redis: { ns: 'default', query: 'redis', cmd: [ 'redis-cli' ] },
|
|
21
|
+
'redis-cli': { ns: 'default', query: 'redis', cmd: [ 'redis-cli' ] },
|
|
22
|
+
valkey: { ns: 'default', query: 'valkey', cmd: [ 'valkey-cli' ] },
|
|
23
|
+
'valkey-cli': { ns: 'default', query: 'valkey', cmd: [ 'valkey-cli' ] },
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const exeCommand = new Command( 'exe' )
|
|
27
|
+
.description( 'Exec into a pod interactively' )
|
|
28
|
+
.argument( '[target]', `shortcut: ${Object.keys( TARGETS ).join( ', ' )}` )
|
|
29
|
+
.action( async ( target ) => {
|
|
30
|
+
await overwhelmGate()
|
|
31
|
+
const cfg = target ?
|
|
32
|
+
TARGETS[target] :
|
|
33
|
+
{ ns: 'default', query: null, cmd: [ 'bash' ] }
|
|
34
|
+
|
|
35
|
+
if ( target && !cfg ) {
|
|
36
|
+
errorExit( `Unknown target: ${target}\nValid: ${Object.keys( TARGETS ).join( ', ' )}` )
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let pods = await listPods( cfg.ns )
|
|
40
|
+
if ( cfg.query ) {
|
|
41
|
+
pods = pods.filter( p => p.name.includes( cfg.query ) )
|
|
42
|
+
}
|
|
43
|
+
if ( !pods.length ) {
|
|
44
|
+
errorExit( `No pods found in ${cfg.ns}${cfg.query ? ` matching "${cfg.query}"` : ''}` )
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const { namespace, name } = pods.length === 1 ?
|
|
48
|
+
pods[0] :
|
|
49
|
+
await selectPod( pods, 'Select pod to exec into' )
|
|
50
|
+
|
|
51
|
+
log( chalk.cyan( `\nexec → ${namespace}/${name}\n` ) )
|
|
52
|
+
try {
|
|
53
|
+
await $( { stdio: 'inherit', verbose: false } )`kubectl exec -it -n ${namespace} ${name} -- ${cfg.cmd}`
|
|
54
|
+
} catch ( e ) {
|
|
55
|
+
if ( e.exitCode !== 130 ) throw e
|
|
56
|
+
}
|
|
57
|
+
} )
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { $ } from 'zx'
|
|
4
|
+
|
|
5
|
+
import { log, errorExit } from '../../Utils.mjs'
|
|
6
|
+
import { overwhelmGate } from '../lib/gate.mjs'
|
|
7
|
+
|
|
8
|
+
const TARGETS = {
|
|
9
|
+
elastic: { ns: 'elastic', svc: 'elasticsearch8', port: 9200 },
|
|
10
|
+
redis: { ns: 'default', svc: 'redis-master', port: 6379 },
|
|
11
|
+
valkey: { ns: 'default', svc: 'valkey-master', port: 6379 },
|
|
12
|
+
prom: { ns: 'prometheus', svc: 'prometheus-operated', port: 9090 },
|
|
13
|
+
alert: { ns: 'prometheus', svc: 'alertmanager-operated', port: 9093 },
|
|
14
|
+
grafana: { ns: 'prometheus', svc: 'prometheus-stack-grafana', port: 3000 },
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const fwdCommand = new Command( 'fwd' )
|
|
18
|
+
.description( 'Port-forward to a cluster endpoint' )
|
|
19
|
+
.argument( '<target>', `target: ${Object.keys( TARGETS ).join( ', ' )}` )
|
|
20
|
+
.option( '-l, --local <port>', 'override local port' )
|
|
21
|
+
.action( async ( target, opts ) => {
|
|
22
|
+
await overwhelmGate()
|
|
23
|
+
const cfg = TARGETS[target]
|
|
24
|
+
if ( !cfg ) {
|
|
25
|
+
errorExit( `Unknown target: ${target}\nValid: ${Object.keys( TARGETS ).join( ', ' )}` )
|
|
26
|
+
}
|
|
27
|
+
const localPort = opts.local ?? cfg.port
|
|
28
|
+
log( chalk.cyan( `\nForwarding localhost:${localPort} → ${cfg.ns}/${cfg.svc}:${cfg.port}` ) )
|
|
29
|
+
log( chalk.gray( 'Ctrl+C to stop\n' ) )
|
|
30
|
+
try {
|
|
31
|
+
await $( { stdio: 'inherit', verbose: false } )`kubectl port-forward -n ${cfg.ns} svc/${cfg.svc} ${localPort}:${cfg.port}`
|
|
32
|
+
} catch ( e ) {
|
|
33
|
+
if ( e.exitCode !== 130 ) throw e
|
|
34
|
+
}
|
|
35
|
+
} )
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { $ } from 'zx'
|
|
4
|
+
|
|
5
|
+
import { log, getDateTimestamp } from '../../Utils.mjs'
|
|
6
|
+
import { overwhelmGate } from '../lib/gate.mjs'
|
|
7
|
+
import { listPods } from '../lib/kubectl.mjs'
|
|
8
|
+
import { selectPod } from '../lib/select.mjs'
|
|
9
|
+
|
|
10
|
+
const PINO = [ '-c', '-t', 'HH:MM:ss.l', '-i', 'severity' ]
|
|
11
|
+
|
|
12
|
+
// Heuristic: strip replicaset+pod hashes for Deployments, ordinal for StatefulSets
|
|
13
|
+
function containerFromPod( podName ) {
|
|
14
|
+
const parts = podName.split( '-' )
|
|
15
|
+
const last = parts.at( -1 )
|
|
16
|
+
const prev = parts.at( -2 )
|
|
17
|
+
const isHash = s => s && /^[a-z0-9]{5,10}$/.test( s )
|
|
18
|
+
if ( isHash( last ) && isHash( prev ) ) return parts.slice( 0, -2 ).join( '-' )
|
|
19
|
+
if ( /^\d+$/.test( last ) ) return parts.slice( 0, -1 ).join( '-' )
|
|
20
|
+
return parts.slice( 0, -1 ).join( '-' )
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function logFile( name, tee ) {
|
|
24
|
+
return typeof tee === 'string' ? tee : `${name}-${getDateTimestamp()}.log`
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function stream( $i, args, { pretty, tee, name } ) {
|
|
28
|
+
if ( pretty && tee ) {
|
|
29
|
+
await $i`kubectl ${args} | tee ${logFile( name, tee )} | pino-pretty ${PINO}`
|
|
30
|
+
} else if ( pretty ) {
|
|
31
|
+
await $i`kubectl ${args} | pino-pretty ${PINO}`
|
|
32
|
+
} else if ( tee ) {
|
|
33
|
+
await $i`kubectl ${args} | tee ${logFile( name, tee )}`
|
|
34
|
+
} else {
|
|
35
|
+
await $i`kubectl ${args}`
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const logCommand = new Command( 'log' )
|
|
40
|
+
.description( 'Stream pod logs with pino-pretty formatting' )
|
|
41
|
+
.argument( '[service]', 'app label selector (app=<service>); interactive pod picker if omitted' )
|
|
42
|
+
.option( '-n, --namespace <ns>', 'namespace', 'default' )
|
|
43
|
+
.option( '-c, --container <name>', 'container name (derived from pod name if omitted)' )
|
|
44
|
+
.option( '--tail <n>', 'initial lines to show', '50' )
|
|
45
|
+
.option( '--tee [file]', 'also write raw logs to a timestamped file' )
|
|
46
|
+
.option( '--no-pretty', 'skip pino-pretty, raw output' )
|
|
47
|
+
.action( async ( service, opts ) => {
|
|
48
|
+
await overwhelmGate()
|
|
49
|
+
const $i = $( { stdio: 'inherit', verbose: false } )
|
|
50
|
+
|
|
51
|
+
if ( service ) {
|
|
52
|
+
const { namespace: ns, tail } = opts
|
|
53
|
+
const args = [ 'logs', '--follow', '-n', ns, '-l', `app=${service}`, '--max-log-requests=16', `--tail=${tail}` ]
|
|
54
|
+
log( chalk.cyan( `\nStreaming ${ns}/app=${service}\n` ) )
|
|
55
|
+
try {
|
|
56
|
+
await stream( $i, args, { pretty: opts.pretty, tee: opts.tee, name: service } )
|
|
57
|
+
} catch ( e ) {
|
|
58
|
+
if ( e.exitCode !== 130 ) throw e
|
|
59
|
+
}
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Interactive pod picker — all namespaces unless -n was given
|
|
64
|
+
const ns = opts.namespace !== 'default' ? opts.namespace : undefined
|
|
65
|
+
const pods = await listPods( ns )
|
|
66
|
+
const { namespace, name: podName } = await selectPod( pods, 'Select a pod to stream' )
|
|
67
|
+
const container = opts.container ?? containerFromPod( podName )
|
|
68
|
+
const args = [ 'logs', '--follow', '-n', namespace, podName, '-c', container, `--tail=${opts.tail}` ]
|
|
69
|
+
|
|
70
|
+
log( chalk.cyan( `\nStreaming ${namespace}/${podName} (container: ${container})\n` ) )
|
|
71
|
+
try {
|
|
72
|
+
await stream( $i, args, { pretty: opts.pretty, tee: opts.tee, name: container } )
|
|
73
|
+
} catch ( e ) {
|
|
74
|
+
if ( e.exitCode !== 130 ) throw e
|
|
75
|
+
}
|
|
76
|
+
} )
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { $ } from 'zx'
|
|
4
|
+
|
|
5
|
+
import { log } from '../../Utils.mjs'
|
|
6
|
+
import { overwhelmGate } from '../lib/gate.mjs'
|
|
7
|
+
import { listDeployments } from '../lib/kubectl.mjs'
|
|
8
|
+
import { selectDeployment } from '../lib/select.mjs'
|
|
9
|
+
|
|
10
|
+
export const rollCommand = new Command( 'roll' )
|
|
11
|
+
.description( 'Rolling restart of one or more deployments' )
|
|
12
|
+
.argument( '[deployments...]', 'deployment names (interactive if omitted)' )
|
|
13
|
+
.option( '-n, --namespace <ns>', 'namespace', 'default' )
|
|
14
|
+
.action( async ( deployments, opts ) => {
|
|
15
|
+
await overwhelmGate()
|
|
16
|
+
|
|
17
|
+
let targets = deployments
|
|
18
|
+
if ( !targets.length ) {
|
|
19
|
+
const all = await listDeployments( opts.namespace )
|
|
20
|
+
targets = [ await selectDeployment( all, 'Select deployment to restart' ) ]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
log( chalk.cyan( '\nrolling restart\n' ) )
|
|
24
|
+
for ( const deploy of targets ) {
|
|
25
|
+
try {
|
|
26
|
+
// sequential by design — report status per deployment as each completes
|
|
27
|
+
// eslint-disable-next-line no-await-in-loop
|
|
28
|
+
await $`kubectl rollout restart deployment ${deploy} -n ${opts.namespace}`
|
|
29
|
+
log( ` ${chalk.green( 'SUCCESS' )} ${deploy}` )
|
|
30
|
+
// eslint-disable-next-line no-await-in-loop
|
|
31
|
+
await $`send-to-slack ${'k8scale roll ' + deploy}`.catch( () => {} )
|
|
32
|
+
} catch {
|
|
33
|
+
log( ` ${chalk.red( 'FAILURE' )} ${deploy}` )
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
log( '' )
|
|
37
|
+
} )
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Command } from 'commander'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { $ } from 'zx'
|
|
4
|
+
|
|
5
|
+
import { log, errorExit } from '../../Utils.mjs'
|
|
6
|
+
import { overwhelmGate } from '../lib/gate.mjs'
|
|
7
|
+
import { listDeployments } from '../lib/kubectl.mjs'
|
|
8
|
+
import { selectDeployment } from '../lib/select.mjs'
|
|
9
|
+
|
|
10
|
+
export const scaleCommand = new Command( 'scale' )
|
|
11
|
+
.description( 'Scale a deployment up or down' )
|
|
12
|
+
.argument( '<direction>', 'up or dn' )
|
|
13
|
+
.argument( '[deployments...]', 'deployment names (interactive if omitted)' )
|
|
14
|
+
.option( '-n, --namespace <ns>', 'namespace', 'default' )
|
|
15
|
+
.action( async ( direction, deployments, opts ) => {
|
|
16
|
+
if ( direction !== 'up' && direction !== 'dn' ) {
|
|
17
|
+
errorExit( 'Direction must be up or dn' )
|
|
18
|
+
}
|
|
19
|
+
await overwhelmGate()
|
|
20
|
+
|
|
21
|
+
const replicas = direction === 'up' ? 1 : 0
|
|
22
|
+
|
|
23
|
+
let targets = deployments
|
|
24
|
+
if ( !targets.length ) {
|
|
25
|
+
const all = await listDeployments( opts.namespace )
|
|
26
|
+
targets = [ await selectDeployment( all, 'Select deployment to scale' ) ]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
log( chalk.cyan( `\nscale ${direction}\n` ) )
|
|
30
|
+
for ( const deploy of targets ) {
|
|
31
|
+
try {
|
|
32
|
+
// sequential by design — report status per deployment as each completes
|
|
33
|
+
// eslint-disable-next-line no-await-in-loop
|
|
34
|
+
await $`kubectl scale --replicas=${replicas} deployment ${deploy} -n ${opts.namespace}`
|
|
35
|
+
log( ` ${chalk.green( 'SUCCESS' )} ${deploy}` )
|
|
36
|
+
// eslint-disable-next-line no-await-in-loop
|
|
37
|
+
await $`send-to-slack ${'k8scale ' + direction + ' ' + deploy}`.catch( () => {} )
|
|
38
|
+
} catch {
|
|
39
|
+
log( ` ${chalk.red( 'FAILURE' )} ${deploy}` )
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
log( '' )
|
|
43
|
+
} )
|