@leverege/build-tools 2.99.2-quinn.1 → 2.100.0
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/package.json +10 -8
- package/src/circle-orb-it/circle-orb-it.mjs +178 -0
- package/src/circle-orb-it/circle-orb.yml +27 -0
- package/src/circle-orb-it/dependabot.yml +7 -0
- package/src/dashboard/DashboardManager.mjs +126 -0
- package/src/dashboard/dashboard.mjs +36 -0
- package/src/dashboard/json/standard/cloudnative-pg-20417-rev4.json +8827 -0
- package/src/dashboard/json/standard/elasticsearch8.json +6404 -0
- package/src/dashboard/json/standard/gpu-autoscaling-dashboard-ui-import.json +319 -0
- package/src/dashboard/json/standard/k8s-api-latency.json +166 -0
- package/src/dashboard/json/standard/noc-rows-12.json +1241 -0
- package/src/dashboard/json/standard/pvc-all-usage.json +943 -0
- package/src/dashboard/json/standard/redis.json +1513 -0
- package/src/dashboard/json/standard/stackdriver.json +624 -0
- package/src/dashboard/json/standard/traefik-op.json +1575 -0
- package/src/dashboard/json/standard/valkey.json +2373 -0
- package/src/helm-charts/eso/helmup.bootstrap +59 -0
- package/src/helm-charts/eso/helmup.plugin +34 -0
- package/src/helm-charts/prom-operator/expand-pvc.sh +54 -0
- package/src/helm-charts/prom-operator/helmup.plugin +1 -1
- package/src/helm-charts/prom-operator/prometheus-stack.yaml.ovh +4 -2
- package/leverege-build-tools-2.21.13.tgz +0 -0
- package/lib/server/build-tools.js +0 -138
- package/lib/server/firebaseDeploy.js +0 -195
- package/lib/server/firebaseServe.js +0 -116
- package/lib/server/getfbcfg.js +0 -25
- package/lib/server/getjson.js +0 -70
- package/lib/server/overwhelm.js +0 -447
- package/lib/server/push-my-chart.js +0 -18
- package/lib/server/refresh-npm-token.js +0 -135
- package/lib/server/tag-release.js +0 -681
- package/lib/server/unleash.js +0 -50
- package/lib/web/build-tools.js +0 -138
- package/lib/web/firebaseDeploy.js +0 -195
- package/lib/web/firebaseServe.js +0 -116
- package/lib/web/getfbcfg.js +0 -25
- package/lib/web/getjson.js +0 -70
- package/lib/web/overwhelm.js +0 -447
- package/lib/web/push-my-chart.js +0 -18
- package/lib/web/refresh-npm-token.js +0 -135
- package/lib/web/tag-release.js +0 -681
- package/lib/web/unleash.js +0 -50
- package/src/circle-orb/circle-orb.mjs +0 -55
- package/src/circle-orb/circle-orb.yml +0 -24
- package/src/circleate.sh +0 -599
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leverege/build-tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.100.0",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -16,18 +16,20 @@
|
|
|
16
16
|
"prepack": "npm exec prepack"
|
|
17
17
|
},
|
|
18
18
|
"bin": {
|
|
19
|
+
"build-and-push": "src/build-and-push.sh",
|
|
19
20
|
"build-cnpg-image": "src/build-cnpg-image.mjs",
|
|
20
21
|
"build-tools": "src/build-tools.mjs",
|
|
21
22
|
"change-up": "src/change-up.sh",
|
|
22
23
|
"chart-compass": "src/chart-compass.mjs",
|
|
23
24
|
"chart-to-registry": "src/chart-to-registry.mjs",
|
|
24
|
-
"
|
|
25
|
+
"circle-orb-it": "src/circle-orb-it/circle-orb-it.mjs",
|
|
25
26
|
"clone-cnpg-from-snapshot": "src/clone-cnpg-from-snapshot.mjs",
|
|
26
27
|
"count-lines-of-code": "src/count-lines-of-code.mjs",
|
|
27
28
|
"decrypt-secrets": "src/decrypt-secrets.sh",
|
|
28
29
|
"dirty-git": "src/dirty-git.sh",
|
|
29
30
|
"docker-to-registry": "src/docker-to-registry.mjs",
|
|
30
31
|
"docker-to-registry-py": "src/docker-to-registry-py.mjs",
|
|
32
|
+
"docker-to-registry.sh": "src/docker-to-registry.sh",
|
|
31
33
|
"dockreate": "src/dockreate.sh",
|
|
32
34
|
"encrypt-secrets": "src/encrypt-secrets.sh",
|
|
33
35
|
"firebaseDeploy": "src/firebaseDeploy.mjs",
|
|
@@ -45,6 +47,7 @@
|
|
|
45
47
|
"helmup": "src/helmup.sh",
|
|
46
48
|
"helmwhat": "src/helmup.sh",
|
|
47
49
|
"hook-and-release": "src/hook-and-release.mjs",
|
|
50
|
+
"dashboard": "src/dashboard/dashboard.mjs",
|
|
48
51
|
"init-my-chart": "src/init-my-chart.mjs",
|
|
49
52
|
"k8scale": "src/k8scale.sh",
|
|
50
53
|
"k8x": "src/k8x.sh",
|
|
@@ -77,11 +80,10 @@
|
|
|
77
80
|
},
|
|
78
81
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
79
82
|
"dependencies": {
|
|
83
|
+
"@kubernetes/client-node": "^1.4.0",
|
|
80
84
|
"@leverege/jsdoc-template": "^1.0.1",
|
|
81
|
-
"@yarnpkg/core": "^4.5.0",
|
|
82
|
-
"@yarnpkg/fslib": "^3.1.4",
|
|
83
85
|
"chalk": "^5.6.2",
|
|
84
|
-
"command-line-args": "^6.0.
|
|
86
|
+
"command-line-args": "^6.0.2",
|
|
85
87
|
"command-line-usage": "^7.0.4",
|
|
86
88
|
"commander": "^14.0.3",
|
|
87
89
|
"deepmerge": "^4.3.1",
|
|
@@ -90,7 +92,7 @@
|
|
|
90
92
|
"find-yarn-workspace-root": "^2.0.0",
|
|
91
93
|
"glob": "^13.0.6",
|
|
92
94
|
"googleapis": "^171.4.0",
|
|
93
|
-
"handlebars": "^4.7.
|
|
95
|
+
"handlebars": "^4.7.9",
|
|
94
96
|
"ignore": "^7.0.5",
|
|
95
97
|
"inquirer": "^13.3.2",
|
|
96
98
|
"js-yaml": "^4.1.1",
|
|
@@ -108,13 +110,13 @@
|
|
|
108
110
|
"superstruct": "^2.0.2",
|
|
109
111
|
"tmp": "^0.2.5",
|
|
110
112
|
"toml": "^3.0.0",
|
|
111
|
-
"yaml": "^2.8.
|
|
113
|
+
"yaml": "^2.8.3",
|
|
112
114
|
"zx": "^8.8.5"
|
|
113
115
|
},
|
|
114
116
|
"devDependencies": {
|
|
115
117
|
"@leverege/eslint-config-leverege": "^5.1.2",
|
|
116
118
|
"chai": "^6.2.2",
|
|
117
119
|
"mocha": "^11.7.5",
|
|
118
|
-
"npm": "^11.
|
|
120
|
+
"npm": "^11.12.1"
|
|
119
121
|
}
|
|
120
122
|
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable security/detect-non-literal-fs-filename */
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import path from 'node:path'
|
|
5
|
+
|
|
6
|
+
import { program } from 'commander'
|
|
7
|
+
import chalk from 'chalk'
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
BUILD_TOOLS_ROOT,
|
|
11
|
+
enableDebug,
|
|
12
|
+
errorExit,
|
|
13
|
+
getGitRootDirectory,
|
|
14
|
+
log,
|
|
15
|
+
proceed,
|
|
16
|
+
} from '../Utils.mjs'
|
|
17
|
+
|
|
18
|
+
const ORB_VERSION = '1.2.4' // Keep this in sync with the version in circle-orb.yml
|
|
19
|
+
const CIRCLE_DIR = '.circleci'
|
|
20
|
+
const CIRCLE_YML = `${CIRCLE_DIR}/config.yml`
|
|
21
|
+
const CIRCLE_YML_TEMPLATE = path.join( BUILD_TOOLS_ROOT, 'circle-orb-it', 'circle-orb.yml' )
|
|
22
|
+
const GITHUB_DIR = '.github'
|
|
23
|
+
const DEPENDABOT_FILE = `${GITHUB_DIR}/dependabot.yml`
|
|
24
|
+
const DEPENDABOT_TEMPLATE = path.join( BUILD_TOOLS_ROOT, 'circle-orb-it', 'dependabot.yml' )
|
|
25
|
+
|
|
26
|
+
program
|
|
27
|
+
.name( 'circle-orb-it' )
|
|
28
|
+
.description( chalk.green( 'Installs or updates CI and GitHub configuration files for a Leverege repository' ) )
|
|
29
|
+
.option( '--debug', 'Enable debug logging' )
|
|
30
|
+
.option( '--dry-run', 'Show what would be written without making changes' )
|
|
31
|
+
.addHelpText( 'after', `
|
|
32
|
+
${chalk.yellow( 'Files managed:' )}
|
|
33
|
+
${chalk.green( CIRCLE_YML )} leverege orb v${ORB_VERSION}
|
|
34
|
+
${chalk.green( DEPENDABOT_FILE )} weekly npm dependency updates
|
|
35
|
+
|
|
36
|
+
${chalk.yellow( 'Notes:' )}
|
|
37
|
+
Run from anywhere inside a git repository.
|
|
38
|
+
Python projects are not supported by the leverege orb.
|
|
39
|
+
The dependabot config is skipped if it already exists.
|
|
40
|
+
|
|
41
|
+
${chalk.yellow( 'Examples:' )}
|
|
42
|
+
${chalk.green( 'circle-orb-it' )} install or upgrade
|
|
43
|
+
${chalk.green( 'circle-orb-it --dry-run' )} preview without writing files
|
|
44
|
+
` )
|
|
45
|
+
.parse()
|
|
46
|
+
|
|
47
|
+
const options = program.opts()
|
|
48
|
+
if ( options.debug ) { enableDebug() }
|
|
49
|
+
|
|
50
|
+
const gitRoot = await getGitRootDirectory()
|
|
51
|
+
if ( !gitRoot ) {
|
|
52
|
+
errorExit( 'This command must be run inside a git repository' )
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Warn if this looks like a Python project since the orb is Node.js only
|
|
56
|
+
if ( fs.existsSync( path.join( gitRoot, 'pyproject.toml' ) ) ) {
|
|
57
|
+
log( chalk.yellow( '\n***WARNING: pyproject.toml detected - the leverege orb is Node.js only' ) )
|
|
58
|
+
await proceed( 'Continue anyway?' )
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------- Circle CI ----------
|
|
62
|
+
|
|
63
|
+
const circleDir = path.join( gitRoot, CIRCLE_DIR )
|
|
64
|
+
const circleYml = path.join( gitRoot, CIRCLE_YML )
|
|
65
|
+
|
|
66
|
+
let writeCircleYml = true
|
|
67
|
+
let redisEnabled = false
|
|
68
|
+
let githubTeam = null
|
|
69
|
+
|
|
70
|
+
if ( fs.existsSync( circleYml ) ) {
|
|
71
|
+
const existing = fs.readFileSync( circleYml, 'utf-8' )
|
|
72
|
+
const alreadyUsesOrb = existing.includes( 'leverege/circle-nodejs-ci' )
|
|
73
|
+
|
|
74
|
+
redisEnabled = existing.includes( 'enable-redis: true' )
|
|
75
|
+
if ( redisEnabled ) {
|
|
76
|
+
log( chalk.yellow( '\nRedis detected in existing config - will be preserved' ) )
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const githubTeamMatch = existing.match( /github-team:\s+(\S+)/ )
|
|
80
|
+
githubTeam = githubTeamMatch ? githubTeamMatch[1] : null
|
|
81
|
+
if ( githubTeam ) {
|
|
82
|
+
log( chalk.yellow( `\ngithub-team "${githubTeam}" detected in existing config - will be preserved` ) )
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if ( alreadyUsesOrb ) {
|
|
86
|
+
if ( existing.includes( `circle-nodejs-ci@${ORB_VERSION}` ) ) {
|
|
87
|
+
log( chalk.green( `\nCircleCI already using leverege/circle-nodejs-ci@${ORB_VERSION} - skipping` ) )
|
|
88
|
+
writeCircleYml = false
|
|
89
|
+
} else {
|
|
90
|
+
log( chalk.yellow( `\nExisting orb config found - will update to @${ORB_VERSION}` ) )
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
log( chalk.yellow( '\nLegacy CircleCI config detected - will replace with orb-based config' ) )
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if ( writeCircleYml ) {
|
|
97
|
+
await proceed( `Overwrite ${CIRCLE_YML}?` )
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ---------- Dependabot ----------
|
|
102
|
+
|
|
103
|
+
const githubDir = path.join( gitRoot, GITHUB_DIR )
|
|
104
|
+
const dependabotPath = path.join( gitRoot, DEPENDABOT_FILE )
|
|
105
|
+
|
|
106
|
+
const writeDependabot = !fs.existsSync( dependabotPath )
|
|
107
|
+
if ( !writeDependabot ) {
|
|
108
|
+
log( chalk.green( '\nDependabot config already exists - skipping' ) )
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------- Nothing to do ----------
|
|
112
|
+
|
|
113
|
+
if ( !writeCircleYml && !writeDependabot ) {
|
|
114
|
+
log( chalk.green( '\nAll configs are up to date - nothing to do\n' ) )
|
|
115
|
+
process.exit( 0 )
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------- Dry run ----------
|
|
119
|
+
|
|
120
|
+
if ( options.dryRun ) {
|
|
121
|
+
if ( writeCircleYml ) {
|
|
122
|
+
let dryRunContent = fs.readFileSync( CIRCLE_YML_TEMPLATE, 'utf-8' )
|
|
123
|
+
if ( redisEnabled ) {
|
|
124
|
+
dryRunContent = dryRunContent
|
|
125
|
+
.replace( 'enable-redis: false', 'enable-redis: true' )
|
|
126
|
+
.replace( '\'{"type":"inMemory"}\'', '\'{"type":"redis"}\'' )
|
|
127
|
+
}
|
|
128
|
+
if ( githubTeam ) {
|
|
129
|
+
dryRunContent = dryRunContent
|
|
130
|
+
.replace( / {10}# github-team: \S+.*/, ` github-team: ${githubTeam}` )
|
|
131
|
+
}
|
|
132
|
+
log( chalk.green( `\n[dry-run] Would write ${CIRCLE_YML}:\n` ) )
|
|
133
|
+
log( dryRunContent )
|
|
134
|
+
}
|
|
135
|
+
if ( writeDependabot ) {
|
|
136
|
+
log( chalk.green( `\n[dry-run] Would write ${DEPENDABOT_FILE}:\n` ) )
|
|
137
|
+
log( fs.readFileSync( DEPENDABOT_TEMPLATE, 'utf-8' ) )
|
|
138
|
+
}
|
|
139
|
+
process.exit( 0 )
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------- Write ----------
|
|
143
|
+
|
|
144
|
+
if ( writeCircleYml ) {
|
|
145
|
+
if ( !fs.existsSync( circleDir ) ) {
|
|
146
|
+
fs.mkdirSync( circleDir, { recursive : true } )
|
|
147
|
+
}
|
|
148
|
+
let circleYmlContent = fs.readFileSync( CIRCLE_YML_TEMPLATE, 'utf-8' )
|
|
149
|
+
if ( redisEnabled ) {
|
|
150
|
+
circleYmlContent = circleYmlContent
|
|
151
|
+
.replace( 'enable-redis: false', 'enable-redis: true' )
|
|
152
|
+
.replace( '\'{"type":"inMemory"}\'', '\'{"type":"redis"}\'' )
|
|
153
|
+
log( chalk.yellow( 'Redis settings preserved in new config' ) )
|
|
154
|
+
}
|
|
155
|
+
if ( githubTeam ) {
|
|
156
|
+
circleYmlContent = circleYmlContent
|
|
157
|
+
.replace( / {10}# github-team: \S+.*/, ` github-team: ${githubTeam}` )
|
|
158
|
+
log( chalk.yellow( `github-team "${githubTeam}" preserved in new config` ) )
|
|
159
|
+
}
|
|
160
|
+
fs.writeFileSync( circleYml, circleYmlContent )
|
|
161
|
+
log( chalk.green( `\nCircleCI config written => ${circleYml}` ) )
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if ( writeDependabot ) {
|
|
165
|
+
if ( !fs.existsSync( githubDir ) ) {
|
|
166
|
+
fs.mkdirSync( githubDir, { recursive : true } )
|
|
167
|
+
}
|
|
168
|
+
fs.writeFileSync( dependabotPath, fs.readFileSync( DEPENDABOT_TEMPLATE, 'utf-8' ) )
|
|
169
|
+
log( chalk.green( `Dependabot config written => ${dependabotPath}` ) )
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const filesToAdd = [
|
|
173
|
+
writeCircleYml ? CIRCLE_YML : null,
|
|
174
|
+
writeDependabot ? DEPENDABOT_FILE : null,
|
|
175
|
+
].filter( Boolean ).join( ' ' )
|
|
176
|
+
|
|
177
|
+
log( chalk.yellow( '\nNext steps:' ) )
|
|
178
|
+
log( ` git add ${filesToAdd} && git commit -m "CHORE migrate to leverege orb ${ORB_VERSION}"\n` )
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
version: 2.1
|
|
2
|
+
|
|
3
|
+
orbs:
|
|
4
|
+
leverege: leverege/circle-nodejs-ci@1.2.4
|
|
5
|
+
|
|
6
|
+
workflows:
|
|
7
|
+
build-and-verify:
|
|
8
|
+
jobs:
|
|
9
|
+
- leverege/install-dependencies:
|
|
10
|
+
context: npm
|
|
11
|
+
# pkg-manager: yarn-berry # uncomment for Yarn Berry repos
|
|
12
|
+
- leverege/code-analysis:
|
|
13
|
+
requires:
|
|
14
|
+
- leverege/install-dependencies
|
|
15
|
+
- leverege/code-coverage:
|
|
16
|
+
coverage-threshold: 70 # percentage threshold for functions, lines, and statements
|
|
17
|
+
context: leverege-gcp # provides GCP_SA_KEY_JSON for tests that need GCP access
|
|
18
|
+
enable-redis: false # set to true if tests require a live Redis connection
|
|
19
|
+
cache-config: '{"type":"inMemory"}' # sets CACHE_CONFIG env var - options: inMemory, redis
|
|
20
|
+
requires:
|
|
21
|
+
- leverege/install-dependencies
|
|
22
|
+
- leverege/generate-docs:
|
|
23
|
+
requires:
|
|
24
|
+
- leverege/install-dependencies
|
|
25
|
+
- leverege/mirror-to-github:
|
|
26
|
+
context: github-mirror
|
|
27
|
+
# github-team: platform # available teams: platform, utilities, pitcrew, recovr
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
import * as k8s from '@kubernetes/client-node'
|
|
5
|
+
import chalk from 'chalk'
|
|
6
|
+
|
|
7
|
+
import { debug, errorExit, log } from '../Utils.mjs'
|
|
8
|
+
|
|
9
|
+
// const FIELD_MANAGER = 'dashboard'
|
|
10
|
+
const GRAFANA_LABEL = 'grafana_dashboard'
|
|
11
|
+
const GRAFANA_FOLDER = 'grafana_folder'
|
|
12
|
+
|
|
13
|
+
/* eslint-disable security/detect-non-literal-fs-filename */
|
|
14
|
+
export default class DashboardManager {
|
|
15
|
+
#api
|
|
16
|
+
#namespace
|
|
17
|
+
#dryRun
|
|
18
|
+
|
|
19
|
+
constructor( { namespace = 'prometheus', dryRun = false } = {} ) {
|
|
20
|
+
this.#namespace = namespace
|
|
21
|
+
this.#dryRun = dryRun
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#getApi() {
|
|
25
|
+
if ( !this.#api ) {
|
|
26
|
+
const kc = new k8s.KubeConfig()
|
|
27
|
+
kc.loadFromDefault()
|
|
28
|
+
this.#api = kc.makeApiClient( k8s.CoreV1Api )
|
|
29
|
+
}
|
|
30
|
+
return this.#api
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async apply( dir ) {
|
|
34
|
+
const folders = fs.readdirSync( dir, { withFileTypes : true } )
|
|
35
|
+
.filter( e => e.isDirectory() )
|
|
36
|
+
|
|
37
|
+
if ( folders.length === 0 ) {
|
|
38
|
+
log( chalk.yellow( `No dashboard subfolders found in ${dir}` ) )
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for ( const folder of folders ) {
|
|
43
|
+
const files = fs.readdirSync( path.join( dir, folder.name ) )
|
|
44
|
+
.filter( f => f.endsWith( '.json' ) )
|
|
45
|
+
|
|
46
|
+
for ( const file of files ) {
|
|
47
|
+
await this.#applyDashboard( folder.name, path.join( dir, folder.name, file ) )
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async list( dir ) {
|
|
53
|
+
if ( !fs.existsSync( dir ) ) {
|
|
54
|
+
errorExit( `Dashboard directory not found: ${dir}` )
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
log( chalk.bold( `Dashboards in: ${dir}` ) )
|
|
58
|
+
|
|
59
|
+
const folders = fs.readdirSync( dir, { withFileTypes : true } )
|
|
60
|
+
.filter( e => e.isDirectory() )
|
|
61
|
+
|
|
62
|
+
if ( folders.length === 0 ) {
|
|
63
|
+
log( chalk.yellow( 'No subfolders found' ) )
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for ( const folder of folders ) {
|
|
68
|
+
const files = fs.readdirSync( path.join( dir, folder.name ) )
|
|
69
|
+
.filter( f => f.endsWith( '.json' ) )
|
|
70
|
+
|
|
71
|
+
log( chalk.bold( `\n[${folder.name}]` ) )
|
|
72
|
+
if ( files.length === 0 ) {
|
|
73
|
+
log( chalk.dim( ' (empty)' ) )
|
|
74
|
+
} else {
|
|
75
|
+
files.forEach( f => log( ` ${chalk.cyan( path.basename( f, '.json' ) )}` ) )
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async #applyDashboard( folder, filePath ) {
|
|
81
|
+
const filename = path.basename( filePath )
|
|
82
|
+
const name = `dashboard-${path.basename( filePath, '.json' )}`
|
|
83
|
+
const json = fs.readFileSync( filePath, 'utf8' )
|
|
84
|
+
|
|
85
|
+
const configMap = {
|
|
86
|
+
apiVersion : 'v1',
|
|
87
|
+
kind : 'ConfigMap',
|
|
88
|
+
metadata : {
|
|
89
|
+
name,
|
|
90
|
+
namespace : this.#namespace,
|
|
91
|
+
labels : { [GRAFANA_LABEL] : '1' },
|
|
92
|
+
annotations : { [GRAFANA_FOLDER] : folder },
|
|
93
|
+
},
|
|
94
|
+
data : { [filename] : json },
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
debug( configMap, `dashboard: ${name}` )
|
|
98
|
+
|
|
99
|
+
if ( this.#dryRun ) {
|
|
100
|
+
log( chalk.yellow( ` [dry-run] ${filename} → [${folder}]` ) )
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const api = this.#getApi()
|
|
105
|
+
try {
|
|
106
|
+
let resourceVersion
|
|
107
|
+
try {
|
|
108
|
+
const existing = await api.readNamespacedConfigMap( { name, namespace : this.#namespace } )
|
|
109
|
+
resourceVersion = existing.metadata.resourceVersion
|
|
110
|
+
} catch {
|
|
111
|
+
// doesn't exist yet — will create below
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if ( resourceVersion ) {
|
|
115
|
+
configMap.metadata.resourceVersion = resourceVersion
|
|
116
|
+
await api.replaceNamespacedConfigMap( { name, namespace : this.#namespace, body : configMap } )
|
|
117
|
+
} else {
|
|
118
|
+
await api.createNamespacedConfigMap( { namespace : this.#namespace, body : configMap } )
|
|
119
|
+
}
|
|
120
|
+
log( chalk.green( ` ✓ ${filename} → [${folder}]` ) )
|
|
121
|
+
} catch ( e ) {
|
|
122
|
+
errorExit( `Failed to apply ${name}: ${e.message}` )
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
import { program } from 'commander'
|
|
6
|
+
import chalk from 'chalk'
|
|
7
|
+
|
|
8
|
+
import { enableDebug, errorExit, log } from '../Utils.mjs'
|
|
9
|
+
|
|
10
|
+
import DashboardManager from './DashboardManager.mjs'
|
|
11
|
+
|
|
12
|
+
const defaultJsonDir = path.join( import.meta.dirname, 'json' )
|
|
13
|
+
|
|
14
|
+
program
|
|
15
|
+
.name( 'dashboard' )
|
|
16
|
+
.description( 'Manage Grafana dashboards as Kubernetes ConfigMaps' )
|
|
17
|
+
.argument( '[dir]', 'Directory of dashboard subfolders', defaultJsonDir )
|
|
18
|
+
.option( '--list', 'List available dashboards without applying' )
|
|
19
|
+
.option( '--dry-run', 'Show what would be applied without making changes' )
|
|
20
|
+
.option( '--debug', 'Enable debug logging' )
|
|
21
|
+
.option( '-n, --namespace <ns>', 'Target Kubernetes namespace', 'prometheus' )
|
|
22
|
+
.parse()
|
|
23
|
+
|
|
24
|
+
const { list, dryRun, debug, namespace } = program.opts()
|
|
25
|
+
const [ dir = defaultJsonDir ] = program.args
|
|
26
|
+
|
|
27
|
+
if ( debug ) enableDebug()
|
|
28
|
+
|
|
29
|
+
const mgr = new DashboardManager( { namespace, dryRun } )
|
|
30
|
+
|
|
31
|
+
if ( list ) {
|
|
32
|
+
await mgr.list( dir ).catch( e => errorExit( e ) )
|
|
33
|
+
} else {
|
|
34
|
+
log( chalk.bold( `Applying dashboards from: ${dir}` ) )
|
|
35
|
+
await mgr.apply( dir ).catch( e => errorExit( e ) )
|
|
36
|
+
}
|