@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
|
@@ -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
|
+
} )
|
package/src/k8s/k8s.mjs
ADDED
|
@@ -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,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:
|
|
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
|
} )
|
package/src/repo-panopticon.mjs
CHANGED
|
@@ -27,8 +27,8 @@ let latestOrbVersion = null
|
|
|
27
27
|
const CHECKS = [
|
|
28
28
|
{
|
|
29
29
|
label : 'cir',
|
|
30
|
-
fn : ( {
|
|
31
|
-
const cfgPath = path.join(
|
|
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(
|
|
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 : '
|
|
84
|
-
fn : ( {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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( '
|
|
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( '
|
|
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
|
|
@@ -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
|
+
}
|
|
@@ -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
|
|
168
|
-
|
|
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( '\
|
|
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
|
-
|
|
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 }
|