@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.
Files changed (38) hide show
  1. package/README.md +0 -15
  2. package/package.json +13 -9
  3. package/src/build-tools.mjs +3 -2
  4. package/src/chart-compass.yaml +6 -1
  5. package/src/circle-orb-it/circle-npm.yml +8 -0
  6. package/src/helm-charts/api-server/values-local.yaml +1 -1
  7. package/src/helm-charts/cnpg-db-psql-stack/cluster.yaml.ovh +1 -1
  8. package/src/helm-charts/cnpg-db-tsdb-basic/cluster.yaml.ovh +1 -1
  9. package/src/helm-charts/cnpg-db-tsdb-dense/cluster.yaml.ovh +1 -1
  10. package/src/helm-charts/llm-prompt-server/values-local.yaml +5 -0
  11. package/src/helm-charts/prom-operator/helmup.plugin +3 -3
  12. package/src/helm-charts/prom-operator/prometheus-stack.yaml.ovh +1 -1
  13. package/src/helm-charts/triton-inference-server/values-local.yaml +28 -0
  14. package/src/helmup.sh +0 -3
  15. package/src/k8s/commands/completion.mjs +100 -0
  16. package/src/k8s/commands/exe.mjs +57 -0
  17. package/src/k8s/commands/fwd.mjs +35 -0
  18. package/src/k8s/commands/log.mjs +76 -0
  19. package/src/k8s/commands/roll.mjs +37 -0
  20. package/src/k8s/commands/scale.mjs +43 -0
  21. package/src/k8s/k8s.mjs +23 -0
  22. package/src/k8s/lib/gate.mjs +9 -0
  23. package/src/k8s/lib/kubectl.mjs +26 -0
  24. package/src/k8s/lib/select.mjs +18 -0
  25. package/src/overwhelm.mjs +1 -1
  26. package/src/repo-panopticon.mjs +16 -12
  27. package/src/service-man/config/Config.mjs +1 -1
  28. package/src/service-man/config/iam-roles/leveregeVertexEndpointUser.json +10 -0
  29. package/src/service-man/config/service-accounts/llm-prompt-server.json +6 -0
  30. package/src/service-man/config/service-accounts/triton-inference-server.json +5 -0
  31. package/src/service-man/gcloud-k8s/ServiceAccountManager.mjs +9 -5
  32. package/src/artifacts/ArtifactPuller.mjs +0 -131
  33. package/src/artifacts/ArtifactPusher.mjs +0 -110
  34. package/src/artifacts/Structs.mjs +0 -98
  35. package/src/artifacts/Utils.mjs +0 -284
  36. package/src/artifacts/pull-artifact.mjs +0 -81
  37. package/src/artifacts/push-artifact.mjs +0 -80
  38. 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.113.5",
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-fix": "eslint --fix",
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": "^5.6.2",
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": "^9.6.1",
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": "^4.1.1",
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": "^11.17.0"
129
+ "npm": "^12.0.1"
126
130
  },
127
131
  "allowScripts": {
128
132
  "unrs-resolver@1.12.2": true
@@ -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
- return list['dist-tags'].latest
127
+ const latest = list['dist-tags'].latest
128
+ return prerelease( latest ) ? undefined : latest
128
129
  } catch ( err ) {
129
130
  console.log( err )
130
131
  }
@@ -22,6 +22,11 @@
22
22
  - transponder-tsdb
23
23
 
24
24
  leverege:
25
+ llm-prompt-server: {}
26
+ overdose: {}
25
27
  pubsub-pulse: {}
26
28
  pusher: {}
27
- overdose: {}
29
+ vms-server: {}
30
+
31
+ system:
32
+ triton-inference-server: {}
@@ -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-pool-r.cnpg-operands"
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"
@@ -55,7 +55,7 @@ spec:
55
55
  size: 16Gi
56
56
 
57
57
  resources:
58
- # limit:
58
+ # limits:
59
59
  # cpu: 1000m
60
60
  # memory: 8Gi
61
61
  requests:
@@ -55,7 +55,7 @@ spec:
55
55
  size: 16Gi
56
56
 
57
57
  resources:
58
- # limit:
58
+ # limits:
59
59
  # cpu: 1000m
60
60
  # memory: 8Gi
61
61
  requests:
@@ -54,7 +54,7 @@ spec:
54
54
  size: 16Gi
55
55
 
56
56
  resources:
57
- # limit:
57
+ # limits:
58
58
  # cpu: 1000m
59
59
  # memory: 8Gi
60
60
  requests:
@@ -0,0 +1,5 @@
1
+ image:
2
+ tag:
3
+
4
+ config:
5
+ LOG_CONFIG: '{"type":"pino","level":"warn"}'
@@ -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="86.2.3"
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.2.1"
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.12.2"
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 \
@@ -7,7 +7,7 @@ crds:
7
7
  enabled: true
8
8
  upgradeJob:
9
9
  enabled: true
10
- forceConflicts: false
10
+ forceConflicts: true
11
11
 
12
12
  kubeControllerManager:
13
13
  enabled: false
@@ -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
@@ -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
  ;;
@@ -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
+ } )