@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.
Files changed (41) hide show
  1. package/README.md +0 -15
  2. package/package.json +15 -10
  3. package/src/bash-funcs +1 -1
  4. package/src/build-tools.mjs +3 -2
  5. package/src/chart-compass.yaml +6 -1
  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/helm-charts/vms-server/values-local.yaml +45 -0
  15. package/src/helmup.sh +5 -4
  16. package/src/k8s/commands/completion.mjs +100 -0
  17. package/src/k8s/commands/cryo.mjs +164 -0
  18. package/src/k8s/commands/exe.mjs +57 -0
  19. package/src/k8s/commands/fwd.mjs +35 -0
  20. package/src/k8s/commands/log.mjs +76 -0
  21. package/src/k8s/commands/roll.mjs +37 -0
  22. package/src/k8s/commands/scale.mjs +43 -0
  23. package/src/k8s/k8s.mjs +26 -0
  24. package/src/k8s/lib/gate.mjs +9 -0
  25. package/src/k8s/lib/kubectl.mjs +26 -0
  26. package/src/k8s/lib/select.mjs +18 -0
  27. package/src/overwhelm.mjs +1 -1
  28. package/src/repo-panopticon.mjs +16 -12
  29. package/src/service-man/config/Config.mjs +1 -1
  30. package/src/service-man/config/iam-roles/leveregeVertexEndpointUser.json +10 -0
  31. package/src/service-man/config/service-accounts/llm-prompt-server.json +6 -0
  32. package/src/service-man/config/service-accounts/triton-inference-server.json +5 -0
  33. package/src/service-man/config/service-accounts/vms-server.json +8 -0
  34. package/src/service-man/gcloud-k8s/ServiceAccountManager.mjs +9 -5
  35. package/src/artifacts/ArtifactPuller.mjs +0 -131
  36. package/src/artifacts/ArtifactPusher.mjs +0 -110
  37. package/src/artifacts/Structs.mjs +0 -98
  38. package/src/artifacts/Utils.mjs +0 -284
  39. package/src/artifacts/pull-artifact.mjs +0 -81
  40. package/src/artifacts/push-artifact.mjs +0 -80
  41. package/src/change-up.sh +0 -27
@@ -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
+ } )
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander'
3
+ import { $ } from 'zx'
4
+
5
+ import { fwdCommand } from './commands/fwd.mjs'
6
+ import { exeCommand } from './commands/exe.mjs'
7
+ import { scaleCommand } from './commands/scale.mjs'
8
+ import { rollCommand } from './commands/roll.mjs'
9
+ import { logCommand } from './commands/log.mjs'
10
+ import { completionCommand } from './commands/completion.mjs'
11
+ import { hibernateCommand, wakeCommand } from './commands/cryo.mjs'
12
+
13
+ $.verbose = false
14
+
15
+ program
16
+ .name( 'k8s' )
17
+ .description( 'Leverege k8s toolkit — forward, exec, scale, roll, log' )
18
+ .addCommand( fwdCommand )
19
+ .addCommand( exeCommand )
20
+ .addCommand( scaleCommand )
21
+ .addCommand( rollCommand )
22
+ .addCommand( logCommand )
23
+ .addCommand( completionCommand )
24
+ .addCommand( hibernateCommand )
25
+ .addCommand( wakeCommand )
26
+ .parse()
@@ -0,0 +1,9 @@
1
+ import { $ } from 'zx'
2
+
3
+ export async function overwhelmGate() {
4
+ try {
5
+ await $({ stdio: 'inherit', verbose: false })`overwhelm`
6
+ } catch {
7
+ process.exit(1)
8
+ }
9
+ }
@@ -0,0 +1,26 @@
1
+ import { $ } from 'zx'
2
+
3
+ export async function listPods(namespace) {
4
+ const result = namespace
5
+ ? await $`kubectl get pods -n ${namespace} --no-headers`
6
+ : await $`kubectl get pods --all-namespaces --no-headers`
7
+
8
+ return result.stdout.trim().split('\n').filter(Boolean).map(line => {
9
+ const cols = line.trim().split(/\s+/)
10
+ if (namespace) {
11
+ const [name, , status] = cols
12
+ return { namespace, name, status }
13
+ }
14
+ const [ns, name, , status] = cols
15
+ return { namespace: ns, name, status }
16
+ })
17
+ }
18
+
19
+ export async function listDeployments(namespace) {
20
+ const result = namespace
21
+ ? await $`kubectl get deploy -n ${namespace} --no-headers -o name`
22
+ : await $`kubectl get deploy --all-namespaces --no-headers -o name`
23
+
24
+ return result.stdout.trim().split('\n').filter(Boolean)
25
+ .map(n => n.replace(/^deployment\.apps\//, ''))
26
+ }
@@ -0,0 +1,18 @@
1
+ import enquirer from 'enquirer'
2
+
3
+ const { AutoComplete } = enquirer
4
+
5
+ export async function selectPod(pods, message = 'Select a pod') {
6
+ const choices = pods.map(p => `${p.namespace}/${p.name} (${p.status})`)
7
+ const prompt = new AutoComplete({ name: 'pod', message, limit: 15, choices })
8
+ const selected = await prompt.run()
9
+ // parse "namespace/podname (status)" back to { namespace, name }
10
+ const [ns, rest] = selected.split('/')
11
+ const name = rest.trim().split(/\s+/)[0]
12
+ return { namespace: ns, name }
13
+ }
14
+
15
+ export async function selectDeployment(deployments, message = 'Select a deployment') {
16
+ const prompt = new AutoComplete({ name: 'deploy', message, limit: 15, choices: deployments })
17
+ return prompt.run()
18
+ }
package/src/overwhelm.mjs CHANGED
@@ -157,7 +157,7 @@ Object.keys( over ).forEach( ( key ) => {
157
157
  } )
158
158
 
159
159
  if ( varErrors.length > 0 ) {
160
- log( chalk.red.bold( '\n***ERROR: unkown variable reference(s) in overwhelm.yaml:\n' ) )
160
+ log( chalk.red.bold( '\n***ERROR: unknown variable reference(s) in overwhelm.yaml:\n' ) )
161
161
  varErrors.forEach( ( error ) => {
162
162
  log( ` ${chalk.yellow.bold( error )}` )
163
163
  } )
@@ -27,8 +27,8 @@ let latestOrbVersion = null
27
27
  const CHECKS = [
28
28
  {
29
29
  label : 'cir',
30
- fn : ( { dir } ) => {
31
- const cfgPath = path.join( dir, '.circleci', 'config.yml' )
30
+ fn : ( { gitRoot } ) => {
31
+ const cfgPath = path.join( gitRoot, '.circleci', 'config.yml' )
32
32
  if ( !fs.existsSync( cfgPath ) ) return { value : SYM_BAD }
33
33
  try {
34
34
  const content = fs.readFileSync( cfgPath, 'utf8' )
@@ -40,7 +40,7 @@ const CHECKS = [
40
40
  if ( major === 1 ) return { value : chalk.yellow( '1' ) }
41
41
  const isLatest = !latestOrbVersion || match[1] === latestOrbVersion
42
42
  const color = isLatest ? chalk.green : chalk.yellow
43
- const hasGHA = fs.existsSync( path.join( dir, '.github', 'workflows', 'ci.yml' ) )
43
+ const hasGHA = fs.existsSync( path.join( gitRoot, '.github', 'workflows', 'ci.yml' ) )
44
44
  return { value : hasGHA ? color( '★' ) : color( `${major}` ) }
45
45
  } catch {
46
46
  return { value : chalk.hex( '#FFA500' )( 'L' ) }
@@ -80,13 +80,16 @@ const CHECKS = [
80
80
  },
81
81
  },
82
82
  {
83
- label : 'bbl',
84
- fn : ( { dir, pkg } ) => {
85
- const configFiles = [ '.babelrc', '.babelrc.js', '.babelrc.json', 'babel.config.js', 'babel.config.json', 'babel.config.cjs' ]
86
- const hasConfig = configFiles.some( f => fs.existsSync( path.join( dir, f ) ) )
87
- const allDeps = { ...( pkg?.dependencies ?? {} ), ...( pkg?.devDependencies ?? {} ) }
88
- const hasDep = Object.keys( allDeps ).some( k => k.includes( 'babel' ) )
89
- return { value : ( hasConfig || hasDep ) ? SYM_BAD : chalk.dim( '–' ) }
83
+ label : 'git',
84
+ fn : ( { gitRoot } ) => {
85
+ try {
86
+ const gitConfig = fs.readFileSync( path.join( gitRoot, '.git', 'config' ), 'utf8' )
87
+ if ( /bitbucket/i.test( gitConfig ) ) return { value : chalk.red( 'bb' ) }
88
+ if ( /github\.com/i.test( gitConfig ) ) return { value : chalk.green( 'gh' ) }
89
+ return { value : chalk.dim( '?' ) }
90
+ } catch {
91
+ return { value : chalk.dim( '?' ) }
92
+ }
90
93
  },
91
94
  },
92
95
  ]
@@ -286,7 +289,7 @@ function renderTable( rows ) {
286
289
  nameHdr,
287
290
  center( 'mgr', 3 ),
288
291
  center( 'pkg', pkgW ),
289
- center( 'git', gitW ),
292
+ center( 'tag', gitW ),
290
293
  center( 'scop', scopeW ),
291
294
  ...CHECKS.map( c => center( c.label, c.label.length ) ),
292
295
  ].join( ' ' )
@@ -333,13 +336,14 @@ program
333
336
  .addHelpText( 'after', `
334
337
  ${chalk.yellow( 'Columns:' )}
335
338
  ${chalk.green( 'pkg' )} version from package.json
336
- ${chalk.green( 'git' )} latest semver git tag (green=match, yellow=mismatch, red=untagged)
339
+ ${chalk.green( 'tag' )} latest semver git tag (green=match, yellow=mismatch, red=untagged)
337
340
  ${chalk.green( 'name' )} orange if dirty: pull/fetch failed (regular repos) or local modifications present (workspace packages)
338
341
  ${chalk.green( 'scope' )} package scope: @org, PRVT, or PUBL
339
342
  ${chalk.green( 'cir' )} CI status: ✘=missing, L=legacy, 1=orb v1, green 2/★=orb v2 current, yellow 2/★=orb v2 stale
340
343
  ${chalk.green( 'ESM' )} "type": "module" in package.json
341
344
  ${chalk.green( 'har' )} .har directory present at repo root
342
345
  ${chalk.green( 'lnt' )} ESLint version from node_modules (✘=no lint script, ?=not installed, yellow=v8, green=v9+)
346
+ ${chalk.green( 'git' )} origin remote: green gh=GitHub, red bb=Bitbucket
343
347
 
344
348
  ${chalk.yellow( 'Monorepos:' )}
345
349
  Workspace packages are discovered automatically — passing a monorepo root
@@ -66,7 +66,7 @@ export default class Config {
66
66
  dumpDir,
67
67
  {
68
68
  recursive : true,
69
- filter : source => !source.endsWith( '.js' )
69
+ filter : source => !source.endsWith( '.mjs' )
70
70
  } )
71
71
 
72
72
  const fh = await fs.open( `${dumpDir}/.nohelm`, 'a' )
@@ -0,0 +1,10 @@
1
+ {
2
+ "description": "Leverege vertex endpoint permission for Gemini",
3
+ "includedPermissions": [
4
+ "aiplatform.endpoints.get",
5
+ "aiplatform.endpoints.list",
6
+ "aiplatform.endpoints.predict"
7
+ ],
8
+ "stage": "BETA",
9
+ "title": "Leverege Vertex Endpoint User"
10
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "roles" : [
3
+ "leveregePubSub",
4
+ "leveregeSecretsViewer"
5
+ ]
6
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "roles" : [
3
+ "leveregeStorageObjectAdmin"
4
+ ]
5
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "roles" : [
3
+ "leveregeBigQueryReader",
4
+ "leveregePubSub",
5
+ "leveregeSecretsViewer",
6
+ "leveregeStorageObjectAdmin"
7
+ ]
8
+ }
@@ -164,8 +164,8 @@ export default class ServiceAccountManager {
164
164
 
165
165
  try {
166
166
  await this.serviceAccountExists( serviceName )
167
- log( `The gcloud SA ${serviceName} already exists - skipping recreation` )
168
- process.exit( 0 )
167
+ log( `The gcloud SA ${serviceName} already exists - skipping` )
168
+ return
169
169
  } catch ( err ) {
170
170
  if ( err.exitCode !== 1 ) {
171
171
  log( chalk.red.bold( `\n ***${err.stderr}` ) )
@@ -211,20 +211,24 @@ export default class ServiceAccountManager {
211
211
 
212
212
  for ( let i = 0; i < saNames.length; i++ ) {
213
213
  const saName = saNames[i]
214
+ let exists = false
214
215
  try {
215
216
  await this.serviceAccountExists( saName ) // eslint-disable-line no-await-in-loop
217
+ exists = true
216
218
  if ( overwrite ) {
217
219
  log( '\nRecreating existing SvcAct =>', chalk.yellow( saName ) )
218
220
  await this.deleteServiceAccount( saName, k8sProject ) // eslint-disable-line no-await-in-loop
221
+ exists = false
219
222
  } else {
220
- log( '\nSkip existing SvcAct =>', chalk.yellow( saName ) )
221
- continue
223
+ log( '\nRebinding existing SvcAct =>', chalk.yellow( saName ) )
222
224
  }
223
225
  } catch ( err ) { // eslint-disable-line no-unused-vars
224
226
  log( '\nCreating new SvcAcct =>', chalk.green( saName ) )
225
227
  }
226
228
 
227
- await this.createServiceAccount( saName, k8sProject ) // eslint-disable-line no-await-in-loop
229
+ if ( !exists ) {
230
+ await this.createServiceAccount( saName, k8sProject ) // eslint-disable-line no-await-in-loop
231
+ }
228
232
 
229
233
  const saRoles = svcCfg.serviceAccounts[saName].roles
230
234
  await this.bindIamRoles( saName, saRoles, k8sProject ) // eslint-disable-line no-await-in-loop
@@ -1,131 +0,0 @@
1
- /* eslint-disable security/detect-non-literal-fs-filename */
2
- import fs from 'node:fs'
3
- import path from 'node:path'
4
-
5
- import { shellCmd, log, debug, err } from '../Utils.mjs'
6
-
7
- import { parseGenericArtifactSchemaToRemoteRelativePath, parseGenericArtifactSchemaToLocalRelativePath, listGenericArtifactFiles, parseGenericArtifactFileToRemoteRelativePath, parseGenericArtifactSchemaToArtifactName } from './Utils.mjs'
8
-
9
- class ArtifactPuller {
10
- constructor( path, artifact ) {
11
- this.path = path
12
- this.artifact = artifact
13
- }
14
-
15
- async pull() {
16
- throw new Error( 'Not implemented' )
17
- }
18
- }
19
-
20
- class GenericArtifactPuller extends ArtifactPuller {
21
-
22
- async downloadFile( remoteRelativePath, localRelativePath ) {
23
- debug( { remoteRelativePath, localRelativePath }, '<==Download File' )
24
-
25
- const isBasenameDifferent = path.basename( localRelativePath ) !== path.basename( remoteRelativePath )
26
-
27
- let tmpLocalRelativePath
28
- if ( isBasenameDifferent ) {
29
- const artifactName = parseGenericArtifactSchemaToArtifactName( this.artifact )
30
- const encodedArtifactName = artifactName.replaceAll( ':', '_' ).replaceAll( '/', '_' ).replaceAll( '%2F', '_' )
31
- tmpLocalRelativePath = path.join( '/', 'tmp', encodedArtifactName )
32
- } else {
33
- await fs.promises.mkdir( path.dirname( localRelativePath ), { recursive : true } )
34
- }
35
-
36
- try {
37
- const command = `gcloud artifacts generic download \
38
- --project=${this.artifact.project} \
39
- --location=${this.artifact.location} \
40
- --repository=${this.artifact.repository} \
41
- --package=${this.artifact.package} \
42
- --version=${this.artifact.version} \
43
- --name="${remoteRelativePath}" \
44
- --destination=${tmpLocalRelativePath || path.dirname( localRelativePath )}`
45
-
46
- debug( { command }, '<==Download File Command' )
47
-
48
- await shellCmd( command )
49
-
50
- if ( isBasenameDifferent ) {
51
- await fs.promises.rename( tmpLocalRelativePath, localRelativePath )
52
- }
53
-
54
- return true
55
- } catch ( error ) {
56
- err( `Error downloading file: ${localRelativePath} ${error.message}` )
57
- return false
58
- }
59
- }
60
-
61
- async downloadDirectory( remoteRelativePath, localRelativePath ) {
62
-
63
- await fs.promises.mkdir( localRelativePath, { recursive : true } )
64
-
65
- const command = `gcloud artifacts generic download \
66
- --project=${this.artifact.project} \
67
- --location=${this.artifact.location} \
68
- --repository=${this.artifact.repository} \
69
- --package=${this.artifact.package} \
70
- --version=${this.artifact.version} \
71
- --destination=${localRelativePath}`
72
-
73
- debug( { command }, '<==Download Directory Command' )
74
-
75
- await shellCmd( command )
76
- }
77
-
78
- async downloadNestedDirectory( remoteRelativePath, localRelativePath ) {
79
- debug( { remoteRelativePath, localRelativePath }, '<==Download Nested Directory' )
80
-
81
- const files = await listGenericArtifactFiles( this.artifact )
82
- await Promise.all(
83
- files
84
- .reduce( ( acc, file ) => {
85
- const remoteFilePath = parseGenericArtifactFileToRemoteRelativePath( file )
86
- if ( remoteFilePath.startsWith( remoteRelativePath ) ) {
87
- const remoteFilePathWithoutRemotePath = remoteFilePath.replace( remoteRelativePath, '' )
88
- const localFilePath = path.join( localRelativePath, remoteFilePathWithoutRemotePath )
89
- acc.push( this.downloadFile( remoteFilePath, localFilePath ) )
90
- }
91
- return acc
92
- }, [] )
93
- )
94
- }
95
-
96
- async pull() {
97
- log( `Pulling artifact: ${this.artifact.name}` )
98
-
99
- debug( { artifact : this.artifact }, '<==Pulling artifact' )
100
-
101
- try {
102
- const remoteRelativePath = parseGenericArtifactSchemaToRemoteRelativePath( this.artifact )
103
- const localRelativePath = parseGenericArtifactSchemaToLocalRelativePath( this.artifact )
104
-
105
- if ( this.artifact.remote_file_path && this.artifact.local_file_path ) {
106
- await this.downloadFile( remoteRelativePath, path.join( this.path, localRelativePath ) )
107
- } else if ( this.artifact.remote_file_path && this.artifact.local_path ) {
108
- await this.downloadFile( remoteRelativePath, path.join( this.path, localRelativePath ) )
109
- } else if ( this.artifact.remote_file_path && !this.artifact.local_path && !this.artifact.local_file_path ) {
110
- await this.downloadFile( remoteRelativePath, path.join( this.path, localRelativePath ) )
111
- } else if ( this.artifact.remote_path && this.artifact.local_path ) {
112
- await this.downloadNestedDirectory( remoteRelativePath, path.join( this.path, localRelativePath ) )
113
- } else if ( this.artifact.remote_path && !this.artifact.local_path && !this.artifact.local_file_path ) {
114
- await this.downloadDirectory( remoteRelativePath, path.join( this.path, localRelativePath ) )
115
- } else if ( !this.artifact.remote_path && !this.artifact.remote_file_path && this.artifact.local_path ) {
116
- await this.downloadDirectory( remoteRelativePath, path.join( this.path, localRelativePath ) )
117
- } else {
118
- throw new Error( 'No valid paths provided' )
119
- }
120
-
121
- log( `Successfully pulled artifact: ${this.artifact.name}` )
122
-
123
- return true
124
- } catch ( error ) {
125
- err( `Error pulling artifact: ${this.artifact.name} ${error.message}` )
126
- }
127
- return false
128
- }
129
- }
130
-
131
- export { ArtifactPuller, GenericArtifactPuller }