@leverege/build-tools 2.101.1 → 2.102.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 +8 -8
- package/src/Utils.mjs +5 -0
- package/src/bash-funcs +27 -0
- package/src/dashboard/DashboardManager.mjs +192 -17
- package/src/dashboard/dashboard.mjs +29 -4
- package/src/dashboard/json/{standard/noc-rows-12.json → devops/devops-noc.json} +801 -61
- package/src/dashboard/json/{standard/pvc-all-usage.json → devops/pvc-usage.json} +2 -2
- package/src/dashboard/json/leverege/leverege-noc.json +2364 -0
- package/src/dashboard/json/{standard/redis.json → leverege/leverege-rule-engine.json} +695 -646
- package/src/dashboard/json/leverege/leverege-services.json +4757 -0
- package/src/dashboard/json/recovr/recovr-locators.json +1227 -0
- package/src/dashboard/json/recovr/recovr-noc.json +1949 -0
- package/src/dashboard/json/recovr/recovr-position-srvr.json +339 -0
- package/src/dashboard/json/recovr/recovr-server.json +1127 -0
- package/src/dashboard/json/siren/siren-services.json +2066 -0
- package/src/dashboard/json/{standard/cloudnative-pg-20417-rev4.json → system/cloudnative-pg.json} +241 -243
- package/src/dashboard/json/{standard → system}/elasticsearch8.json +123 -72
- package/src/dashboard/json/system/stackdriver.json +1189 -0
- package/src/dashboard/json/{standard/traefik-op.json → system/traefik.json} +44 -56
- package/src/dashboard/json/{standard → system}/valkey.json +91 -86
- package/src/dashboard/json/{standard → tpi}/gpu-autoscaling-dashboard-ui-import.json +1 -1
- package/src/dashboard/json/tpi/leverege-noc.json +3624 -0
- package/src/helm-charts/cnpg-operator/helmup.bootstrap +19 -21
- package/src/helm-charts/eso/helmup.bootstrap +4 -2
- package/src/helm-charts/prom-operator/README-CRD-UPGRADES.md +105 -0
- package/src/helm-charts/prom-operator/helmup.bootstrap +22 -14
- package/src/helm-charts/prom-operator/helmup.plugin +1 -1
- package/src/helm-charts/prom-operator/prometheus-stack.yaml.ovh +23 -45
- package/src/helm-charts/prom-operator/rules/gke-maintenance-rules.yaml +19 -0
- package/src/helm-charts/prom-operator/stackdriver-exporter.yaml.ovh +3 -2
- package/src/helm-charts/valkey/valkey-local.yaml +1 -0
- package/src/helm-charts/velero/helmup.bootstrap +2 -0
- package/src/helmup.sh +31 -4
- package/src/init-my-chart/Chartwright.mjs +2 -2
- package/src/init-my-chart/leaf-chart-templates/templates/extraobjects.yaml +1 -0
- package/src/init-my-chart/leaf-chart-templates/templates/ingressroute.yaml +1 -0
- package/src/overwhelm.mjs +5 -5
- package/src/service-man/config/secrets/npmrc.json +14 -0
- package/src/service-man/config/secrets/tsdb-dense-password.json +13 -0
- package/src/dashboard/json/standard/stackdriver.json +0 -624
- package/src/docker-to-registry-py.mjs +0 -287
- /package/src/dashboard/json/{standard → devops}/k8s-api-latency.json +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leverege/build-tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.102.0",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -75,8 +75,8 @@
|
|
|
75
75
|
},
|
|
76
76
|
"author": "Leverege Devs",
|
|
77
77
|
"engines": {
|
|
78
|
-
"node": "^22.11 ||
|
|
79
|
-
"npm": ">=11.6"
|
|
78
|
+
"node": "^22.11 || >=24",
|
|
79
|
+
"npm": ">=10.2 || >=11.6"
|
|
80
80
|
},
|
|
81
81
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
82
82
|
"dependencies": {
|
|
@@ -94,29 +94,29 @@
|
|
|
94
94
|
"googleapis": "^171.4.0",
|
|
95
95
|
"handlebars": "^4.7.9",
|
|
96
96
|
"ignore": "^7.0.5",
|
|
97
|
-
"inquirer": "^13.4.
|
|
97
|
+
"inquirer": "^13.4.3",
|
|
98
98
|
"js-yaml": "^4.1.1",
|
|
99
99
|
"jsdoc": "^4.0.5",
|
|
100
100
|
"ms": "^2.1.3",
|
|
101
101
|
"npm-registry-fetch": "^19.1.1",
|
|
102
|
-
"ora": "^9.
|
|
102
|
+
"ora": "^9.4.0",
|
|
103
103
|
"p-limit": "^7.3.0",
|
|
104
104
|
"package-up": "^5.0.0",
|
|
105
105
|
"readline-sync": "^1.4.10",
|
|
106
|
-
"semver": "^7.
|
|
106
|
+
"semver": "^7.8.0",
|
|
107
107
|
"shell-quote": "^1.8.3",
|
|
108
108
|
"simple-git": "^3.36.0",
|
|
109
109
|
"sloc": "^0.3.2",
|
|
110
110
|
"superstruct": "^2.0.2",
|
|
111
111
|
"tmp": "^0.2.5",
|
|
112
112
|
"toml": "^3.0.0",
|
|
113
|
-
"yaml": "^2.
|
|
113
|
+
"yaml": "^2.9.0",
|
|
114
114
|
"zx": "^8.8.5"
|
|
115
115
|
},
|
|
116
116
|
"devDependencies": {
|
|
117
117
|
"@leverege/eslint-config-leverege": "^5.1.2",
|
|
118
118
|
"chai": "^6.2.2",
|
|
119
119
|
"mocha": "^11.7.5",
|
|
120
|
-
"npm": "^11.
|
|
120
|
+
"npm": "^11.14.1"
|
|
121
121
|
}
|
|
122
122
|
}
|
package/src/Utils.mjs
CHANGED
|
@@ -560,6 +560,11 @@ export const parseHelmChart = async ( helmroot = './helm' ) => {
|
|
|
560
560
|
} )
|
|
561
561
|
|
|
562
562
|
depsOutOfSync = mismatches.length > 0
|
|
563
|
+
|
|
564
|
+
// One final check - if the helm/charts dir is missing then we also need to update dependencies
|
|
565
|
+
if ( hasDependencies && !chartFiles.includes( 'charts' ) ) {
|
|
566
|
+
depsOutOfSync = true
|
|
567
|
+
}
|
|
563
568
|
}
|
|
564
569
|
/* eslint-enable security/detect-non-literal-fs-filename */
|
|
565
570
|
|
package/src/bash-funcs
CHANGED
|
@@ -229,6 +229,33 @@ function waitForIAM() {
|
|
|
229
229
|
done
|
|
230
230
|
}
|
|
231
231
|
|
|
232
|
+
# waitForSAIAM SVC_ACCT PROJECT ROLE MEMBER [TIMEOUT_SECONDS]
|
|
233
|
+
# Like waitForIAM but checks the SA's own IAM policy (for bindings like workloadIdentityUser
|
|
234
|
+
# that are set via `gcloud iam service-accounts add-iam-policy-binding`, not project-level).
|
|
235
|
+
function waitForSAIAM() {
|
|
236
|
+
local svcAcct="$1" project="$2" role="$3" member="$4" timeout="${5:-120}"
|
|
237
|
+
local svcAcctEmail="${svcAcct}@${project}.iam.gserviceaccount.com"
|
|
238
|
+
local start=$(date +%s) delay=2
|
|
239
|
+
|
|
240
|
+
echo "⏳ waiting for SA IAM binding ${role} → ${member} on ${svcAcctEmail} (timeout ${timeout}s)..." >&2
|
|
241
|
+
while true; do
|
|
242
|
+
local found
|
|
243
|
+
found="$(gcloud iam service-accounts get-iam-policy "${svcAcctEmail}" \
|
|
244
|
+
--flatten="bindings[].members" \
|
|
245
|
+
--filter="bindings.role=${role} AND bindings.members=${member}" \
|
|
246
|
+
--format="value(bindings.role)" 2> "$DEVNULL")"
|
|
247
|
+
if [[ -n "$found" ]]; then
|
|
248
|
+
echo "✅ SA IAM binding active: ${role} → ${member}" >&2
|
|
249
|
+
return 0
|
|
250
|
+
fi
|
|
251
|
+
if (( $(date +%s) - start >= timeout )); then
|
|
252
|
+
echo "❌ timed out waiting for SA IAM binding ${role} → ${member}" >&2
|
|
253
|
+
return 1
|
|
254
|
+
fi
|
|
255
|
+
sleep "$delay"; (( delay = delay < 20 ? delay * 2 : 20 ))
|
|
256
|
+
done
|
|
257
|
+
}
|
|
258
|
+
|
|
232
259
|
function removeHelmRepo(){
|
|
233
260
|
[ ! -z "$SKIP_RM_HELM_REPO" ] && return
|
|
234
261
|
helm repo remove $1 &> $DEVNULL
|
|
@@ -9,16 +9,25 @@ import { debug, errorExit, log } from '../Utils.mjs'
|
|
|
9
9
|
// const FIELD_MANAGER = 'dashboard'
|
|
10
10
|
const GRAFANA_LABEL = 'grafana_dashboard'
|
|
11
11
|
const GRAFANA_FOLDER = 'grafana_folder'
|
|
12
|
+
const GRAFANA_LOCAL = 'grafana_dashboard_local'
|
|
12
13
|
|
|
13
14
|
/* eslint-disable security/detect-non-literal-fs-filename */
|
|
14
15
|
export default class DashboardManager {
|
|
15
16
|
#api
|
|
16
17
|
#namespace
|
|
17
18
|
#dryRun
|
|
19
|
+
#prune
|
|
20
|
+
#folders
|
|
21
|
+
#localDir
|
|
22
|
+
#pruneLocal
|
|
18
23
|
|
|
19
|
-
constructor( { namespace = 'prometheus', dryRun = false } = {} ) {
|
|
24
|
+
constructor( { namespace = 'prometheus', dryRun = false, prune = false, folders = [], localDir = null, pruneLocal = false } = {} ) {
|
|
20
25
|
this.#namespace = namespace
|
|
21
26
|
this.#dryRun = dryRun
|
|
27
|
+
this.#prune = prune
|
|
28
|
+
this.#folders = folders
|
|
29
|
+
this.#localDir = localDir
|
|
30
|
+
this.#pruneLocal = pruneLocal
|
|
22
31
|
}
|
|
23
32
|
|
|
24
33
|
#getApi() {
|
|
@@ -31,21 +40,61 @@ export default class DashboardManager {
|
|
|
31
40
|
}
|
|
32
41
|
|
|
33
42
|
async apply( dir ) {
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
if ( !fs.existsSync( dir ) ) {
|
|
44
|
+
errorExit( `Dashboard directory not found: ${dir}` )
|
|
45
|
+
}
|
|
36
46
|
|
|
37
|
-
|
|
38
|
-
|
|
47
|
+
const entries = fs.readdirSync( dir, { withFileTypes : true } )
|
|
48
|
+
const folders = entries.filter( e => e.isDirectory() )
|
|
49
|
+
const jsonFiles = entries.filter( e => e.isFile() && e.name.endsWith( '.json' ) )
|
|
50
|
+
|
|
51
|
+
if ( folders.length === 0 && jsonFiles.length === 0 ) {
|
|
52
|
+
log( chalk.yellow( `No dashboards or subfolders found in ${dir}` ) )
|
|
39
53
|
return
|
|
40
54
|
}
|
|
41
55
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
56
|
+
const applied = new Set()
|
|
57
|
+
|
|
58
|
+
if ( folders.length === 0 ) {
|
|
59
|
+
// leaf directory — apply JSON files using basename as the Grafana folder name
|
|
60
|
+
const folderName = path.basename( dir )
|
|
61
|
+
for ( const file of jsonFiles ) {
|
|
62
|
+
const name = await this.#applyDashboard( folderName, path.join( dir, file.name ) )
|
|
63
|
+
if ( name ) applied.add( name )
|
|
64
|
+
}
|
|
65
|
+
if ( this.#prune ) await this.#pruneOrphans( applied, folderName )
|
|
66
|
+
} else {
|
|
67
|
+
// filter to configured folders if DASHBOARDS was set in overwhelm.yaml
|
|
68
|
+
const activeFolders = this.#folders.length > 0 ?
|
|
69
|
+
folders.filter( f => this.#folders.includes( f.name ) ) :
|
|
70
|
+
folders
|
|
71
|
+
|
|
72
|
+
if ( this.#folders.length > 0 && activeFolders.length < this.#folders.length ) {
|
|
73
|
+
const missing = this.#folders.filter( f => !folders.find( d => d.name === f ) )
|
|
74
|
+
missing.forEach( f => log( chalk.yellow( ` [warn] requested folder not found: ${f}` ) ) )
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for ( const folder of activeFolders ) {
|
|
78
|
+
const files = fs.readdirSync( path.join( dir, folder.name ) )
|
|
79
|
+
.filter( f => f.endsWith( '.json' ) )
|
|
80
|
+
|
|
81
|
+
for ( const file of files ) {
|
|
82
|
+
const name = await this.#applyDashboard( folder.name, path.join( dir, folder.name, file ) )
|
|
83
|
+
if ( name ) applied.add( name )
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if ( this.#prune ) await this.#pruneOrphans( applied )
|
|
88
|
+
}
|
|
45
89
|
|
|
46
|
-
|
|
47
|
-
|
|
90
|
+
// Apply local dashboards if --local-dir was specified
|
|
91
|
+
if ( this.#localDir ) {
|
|
92
|
+
if ( !fs.existsSync( this.#localDir ) ) {
|
|
93
|
+
errorExit( `Local dashboard directory not found: ${this.#localDir}` )
|
|
48
94
|
}
|
|
95
|
+
log( chalk.bold( `Applying local dashboards from: ${this.#localDir}` ) )
|
|
96
|
+
const localApplied = await this.#applyDir( this.#localDir, true )
|
|
97
|
+
if ( this.#pruneLocal ) await this.#pruneLocalOrphans( localApplied )
|
|
49
98
|
}
|
|
50
99
|
}
|
|
51
100
|
|
|
@@ -64,7 +113,11 @@ export default class DashboardManager {
|
|
|
64
113
|
return
|
|
65
114
|
}
|
|
66
115
|
|
|
67
|
-
|
|
116
|
+
const activeFolders = this.#folders.length > 0 ?
|
|
117
|
+
folders.filter( f => this.#folders.includes( f.name ) ) :
|
|
118
|
+
folders
|
|
119
|
+
|
|
120
|
+
for ( const folder of activeFolders ) {
|
|
68
121
|
const files = fs.readdirSync( path.join( dir, folder.name ) )
|
|
69
122
|
.filter( f => f.endsWith( '.json' ) )
|
|
70
123
|
|
|
@@ -75,20 +128,74 @@ export default class DashboardManager {
|
|
|
75
128
|
files.forEach( f => log( ` ${chalk.cyan( path.basename( f, '.json' ) )}` ) )
|
|
76
129
|
}
|
|
77
130
|
}
|
|
131
|
+
|
|
132
|
+
if ( this.#localDir && fs.existsSync( this.#localDir ) ) {
|
|
133
|
+
log( chalk.bold( `\nLocal dashboards in: ${this.#localDir}` ) )
|
|
134
|
+
const localFolders = fs.readdirSync( this.#localDir, { withFileTypes : true } )
|
|
135
|
+
.filter( e => e.isDirectory() )
|
|
136
|
+
|
|
137
|
+
for ( const folder of localFolders ) {
|
|
138
|
+
const files = fs.readdirSync( path.join( this.#localDir, folder.name ) )
|
|
139
|
+
.filter( f => f.endsWith( '.json' ) )
|
|
140
|
+
|
|
141
|
+
log( chalk.bold( `\n[${folder.name}] (local)` ) )
|
|
142
|
+
if ( files.length === 0 ) {
|
|
143
|
+
log( chalk.dim( ' (empty)' ) )
|
|
144
|
+
} else {
|
|
145
|
+
files.forEach( f => log( ` ${chalk.cyan( path.basename( f, '.json' ) )}` ) )
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
78
149
|
}
|
|
79
150
|
|
|
80
|
-
|
|
151
|
+
// Scans a directory for JSON files (leaf mode) or subfolders and applies all dashboards.
|
|
152
|
+
// Returns the Set of ConfigMap names applied.
|
|
153
|
+
async #applyDir( dir, local = false ) {
|
|
154
|
+
const entries = fs.readdirSync( dir, { withFileTypes : true } )
|
|
155
|
+
const subDirs = entries.filter( e => e.isDirectory() )
|
|
156
|
+
const jsonFiles = entries.filter( e => e.isFile() && e.name.endsWith( '.json' ) )
|
|
157
|
+
const applied = new Set()
|
|
158
|
+
|
|
159
|
+
if ( subDirs.length === 0 ) {
|
|
160
|
+
const folderName = path.basename( dir )
|
|
161
|
+
for ( const file of jsonFiles ) {
|
|
162
|
+
const name = await this.#applyDashboard( folderName, path.join( dir, file.name ), local )
|
|
163
|
+
if ( name ) applied.add( name )
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
for ( const folder of subDirs ) {
|
|
167
|
+
const files = fs.readdirSync( path.join( dir, folder.name ) )
|
|
168
|
+
.filter( f => f.endsWith( '.json' ) )
|
|
169
|
+
|
|
170
|
+
for ( const file of files ) {
|
|
171
|
+
const name = await this.#applyDashboard( folder.name, path.join( dir, folder.name, file ), local )
|
|
172
|
+
if ( name ) applied.add( name )
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return applied
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async #applyDashboard( folder, filePath, local = false ) {
|
|
81
181
|
const filename = path.basename( filePath )
|
|
82
|
-
const
|
|
182
|
+
const slug = path.basename( filePath, '.json' )
|
|
183
|
+
.toLowerCase()
|
|
184
|
+
.replace( /[^a-z0-9.-]+/g, '-' )
|
|
185
|
+
.replace( /^-+|-+$/g, '' )
|
|
186
|
+
const name = `dashboard-${local ? 'local-' : ''}${slug}`
|
|
83
187
|
const json = fs.readFileSync( filePath, 'utf8' )
|
|
84
188
|
|
|
189
|
+
const labels = { [GRAFANA_LABEL] : '1' }
|
|
190
|
+
if ( local ) labels[GRAFANA_LOCAL] = '1'
|
|
191
|
+
|
|
85
192
|
const configMap = {
|
|
86
193
|
apiVersion : 'v1',
|
|
87
194
|
kind : 'ConfigMap',
|
|
88
195
|
metadata : {
|
|
89
196
|
name,
|
|
90
197
|
namespace : this.#namespace,
|
|
91
|
-
labels
|
|
198
|
+
labels,
|
|
92
199
|
annotations : { [GRAFANA_FOLDER] : folder },
|
|
93
200
|
},
|
|
94
201
|
data : { [filename] : json },
|
|
@@ -97,8 +204,8 @@ export default class DashboardManager {
|
|
|
97
204
|
debug( configMap, `dashboard: ${name}` )
|
|
98
205
|
|
|
99
206
|
if ( this.#dryRun ) {
|
|
100
|
-
log( chalk.yellow( ` [dry-run] ${filename} → [${folder}]` ) )
|
|
101
|
-
return
|
|
207
|
+
log( chalk.yellow( ` [dry-run] ${filename} → [${folder}]${local ? ' (local)' : ''}` ) )
|
|
208
|
+
return name
|
|
102
209
|
}
|
|
103
210
|
|
|
104
211
|
const api = this.#getApi()
|
|
@@ -117,10 +224,78 @@ export default class DashboardManager {
|
|
|
117
224
|
} else {
|
|
118
225
|
await api.createNamespacedConfigMap( { namespace : this.#namespace, body : configMap } )
|
|
119
226
|
}
|
|
120
|
-
log( chalk.green( ` ✓ ${filename} → [${folder}]` ) )
|
|
227
|
+
log( chalk.green( ` ✓ ${filename} → [${folder}]${local ? ' (local)' : ''}` ) )
|
|
121
228
|
} catch ( e ) {
|
|
122
229
|
errorExit( `Failed to apply ${name}: ${e.message}` )
|
|
123
230
|
}
|
|
231
|
+
return name
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async #pruneOrphans( applied, folderFilter = null ) {
|
|
235
|
+
const api = this.#getApi()
|
|
236
|
+
let configMaps
|
|
237
|
+
try {
|
|
238
|
+
const res = await api.listNamespacedConfigMap( {
|
|
239
|
+
namespace : this.#namespace,
|
|
240
|
+
labelSelector : `${GRAFANA_LABEL}=1`,
|
|
241
|
+
} )
|
|
242
|
+
configMaps = res.items
|
|
243
|
+
} catch ( e ) {
|
|
244
|
+
log( chalk.yellow( ` [prune] could not list ConfigMaps: ${e.message}` ) )
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const orphans = configMaps.filter( ( cm ) => {
|
|
249
|
+
if ( applied.has( cm.metadata.name ) ) return false
|
|
250
|
+
if ( cm.metadata.labels?.[GRAFANA_LOCAL] === '1' ) return false // leave local CMs to pruneLocal
|
|
251
|
+
if ( folderFilter && cm.metadata.annotations?.[GRAFANA_FOLDER] !== folderFilter ) return false
|
|
252
|
+
return true
|
|
253
|
+
} )
|
|
254
|
+
|
|
255
|
+
for ( const cm of orphans ) {
|
|
256
|
+
const name = cm.metadata.name
|
|
257
|
+
if ( this.#dryRun ) {
|
|
258
|
+
log( chalk.yellow( ` [dry-run] prune ${name}` ) )
|
|
259
|
+
continue
|
|
260
|
+
}
|
|
261
|
+
try {
|
|
262
|
+
await api.deleteNamespacedConfigMap( { name, namespace : this.#namespace } )
|
|
263
|
+
log( chalk.red( ` ✗ pruned ${name}` ) )
|
|
264
|
+
} catch ( e ) {
|
|
265
|
+
log( chalk.yellow( ` [prune] failed to delete ${name}: ${e.message}` ) )
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async #pruneLocalOrphans( applied ) {
|
|
271
|
+
const api = this.#getApi()
|
|
272
|
+
let configMaps
|
|
273
|
+
try {
|
|
274
|
+
const res = await api.listNamespacedConfigMap( {
|
|
275
|
+
namespace : this.#namespace,
|
|
276
|
+
labelSelector : `${GRAFANA_LOCAL}=1`,
|
|
277
|
+
} )
|
|
278
|
+
configMaps = res.items
|
|
279
|
+
} catch ( e ) {
|
|
280
|
+
log( chalk.yellow( ` [prune-local] could not list ConfigMaps: ${e.message}` ) )
|
|
281
|
+
return
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const orphans = configMaps.filter( cm => !applied.has( cm.metadata.name ) )
|
|
285
|
+
|
|
286
|
+
for ( const cm of orphans ) {
|
|
287
|
+
const name = cm.metadata.name
|
|
288
|
+
if ( this.#dryRun ) {
|
|
289
|
+
log( chalk.yellow( ` [dry-run] prune-local ${name}` ) )
|
|
290
|
+
continue
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
await api.deleteNamespacedConfigMap( { name, namespace : this.#namespace } )
|
|
294
|
+
log( chalk.red( ` ✗ pruned (local) ${name}` ) )
|
|
295
|
+
} catch ( e ) {
|
|
296
|
+
log( chalk.yellow( ` [prune-local] failed to delete ${name}: ${e.message}` ) )
|
|
297
|
+
}
|
|
298
|
+
}
|
|
124
299
|
}
|
|
125
300
|
|
|
126
301
|
}
|
|
@@ -5,7 +5,7 @@ import path from 'node:path'
|
|
|
5
5
|
import { program } from 'commander'
|
|
6
6
|
import chalk from 'chalk'
|
|
7
7
|
|
|
8
|
-
import { enableDebug, errorExit, log } from '../Utils.mjs'
|
|
8
|
+
import { enableDebug, errorExit, log, shellCmd } from '../Utils.mjs'
|
|
9
9
|
|
|
10
10
|
import DashboardManager from './DashboardManager.mjs'
|
|
11
11
|
|
|
@@ -17,16 +17,41 @@ program
|
|
|
17
17
|
.argument( '[dir]', 'Directory of dashboard subfolders', defaultJsonDir )
|
|
18
18
|
.option( '--list', 'List available dashboards without applying' )
|
|
19
19
|
.option( '--dry-run', 'Show what would be applied without making changes' )
|
|
20
|
+
.option( '--prune', 'Delete ConfigMaps for dashboards no longer on disk' )
|
|
21
|
+
.option( '--local-dir <path>', 'Additional local directory of dashboards to apply alongside official ones' )
|
|
22
|
+
.option( '--prune-local', 'Delete ConfigMaps for local dashboards no longer on disk' )
|
|
20
23
|
.option( '--debug', 'Enable debug logging' )
|
|
21
24
|
.option( '-n, --namespace <ns>', 'Target Kubernetes namespace', 'prometheus' )
|
|
22
25
|
.parse()
|
|
23
26
|
|
|
24
|
-
const { list, dryRun, debug, namespace } = program.opts()
|
|
25
|
-
const [
|
|
27
|
+
const { list, dryRun, prune, localDir, pruneLocal, debug, namespace } = program.opts()
|
|
28
|
+
const [ rawDir ] = program.args
|
|
29
|
+
const dir = rawDir ?
|
|
30
|
+
path.isAbsolute( rawDir ) ? rawDir : path.join( defaultJsonDir, rawDir ) :
|
|
31
|
+
defaultJsonDir
|
|
26
32
|
|
|
27
33
|
if ( debug ) enableDebug()
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
// Read DASHBOARDS from overwhelm.yaml for the current context (exits early, no interactive prompts).
|
|
36
|
+
// If the key is absent or overwhelm.yaml is not present, all folders are loaded.
|
|
37
|
+
let folders = []
|
|
38
|
+
try {
|
|
39
|
+
const raw = ( await shellCmd( 'overwhelm -k DASHBOARDS' ) ).trim()
|
|
40
|
+
if ( raw ) {
|
|
41
|
+
folders = raw.split( ',' ).map( f => f.trim() ).filter( Boolean )
|
|
42
|
+
log( chalk.dim( `Dashboard folders from overwhelm: ${folders.join( ', ' )}` ) )
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// DASHBOARDS not configured — load all folders
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
await shellCmd( 'overwhelm', { stdio : 'inherit' } )
|
|
50
|
+
} catch ( err ) {
|
|
51
|
+
process.exit( err.exitCode )
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const mgr = new DashboardManager( { namespace, dryRun, prune, folders, localDir, pruneLocal } )
|
|
30
55
|
|
|
31
56
|
if ( list ) {
|
|
32
57
|
await mgr.list( dir ).catch( e => errorExit( e ) )
|