@leverege/build-tools 2.101.2 → 2.103.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/.vscode/launch.json +10 -0
- package/README.md +22 -9
- package/package.json +10 -13
- package/src/Utils.mjs +2 -0
- package/src/chart-to-registry.mjs +12 -1
- package/src/circle-orb-it/{circle-orb.yml → circle-npm.yml} +6 -3
- package/src/circle-orb-it/circle-orb-it.mjs +16 -11
- package/src/circle-orb-it/circle-yarn.yml +25 -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/prom-operator/helmup.plugin +1 -1
- package/src/helm-charts/prom-operator/prometheus-stack.yaml.ovh +15 -0
- package/src/helm-charts/prom-operator/rules/gke-maintenance-rules.yaml +19 -0
- package/src/helm-charts/prom-operator/stackdriver-exporter.yaml.ovh +2 -1
- package/src/helm-charts/valkey/valkey-local.yaml +1 -0
- package/src/helmup.sh +24 -1
- package/src/init-my-chart/Chartwright.mjs +1 -1
- package/src/init-my-chart/leaf-chart-templates/templates/extraobjects.yaml +1 -0
- package/src/overwhelm.mjs +5 -5
- package/src/push-my-chart.mjs +13 -4
- package/src/repo-panopticon.mjs +415 -0
- package/src/service-man/config/secrets/npmrc.json +1 -0
- package/src/service-man/config/secrets/tsdb-dense-password.json +13 -0
- package/src/dashboard/json/standard/stackdriver.json +0 -624
- package/src/pkgck.sh +0 -158
- /package/src/dashboard/json/{standard → devops}/k8s-api-latency.json +0 -0
package/.vscode/launch.json
CHANGED
|
@@ -58,6 +58,16 @@
|
|
|
58
58
|
"runtimeExecutable": "node",
|
|
59
59
|
"console": "integratedTerminal"
|
|
60
60
|
},
|
|
61
|
+
{
|
|
62
|
+
"name": "chart-to-registry rsrc-srvr",
|
|
63
|
+
"type": "node",
|
|
64
|
+
"request": "launch",
|
|
65
|
+
"program": "${workspaceFolder}/src/chart-to-registry.mjs",
|
|
66
|
+
"args": [],
|
|
67
|
+
"cwd": "/Users/john/Work/leverege/cicd/@plt/resource-server",
|
|
68
|
+
"runtimeExecutable": "node",
|
|
69
|
+
"console": "integratedTerminal"
|
|
70
|
+
},
|
|
61
71
|
{
|
|
62
72
|
"name": "ingestor in monorepo",
|
|
63
73
|
"type": "node",
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Most tools assume the following are installed and configured:
|
|
|
16
16
|
- `kubectl` (with a valid cluster context)
|
|
17
17
|
- `helm` (v3+)
|
|
18
18
|
- `fzf` (for interactive pod selection in `k8x` and `klog`)
|
|
19
|
-
- Node.js 22+
|
|
19
|
+
- Node.js 22+ - prefer 24
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -85,13 +85,15 @@ init-my-chart [--dry-run] [--debug]
|
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
**`chart-to-registry`** — Pushes Helm charts to GCP Artifact Registry with version
|
|
88
|
-
checking and dependency resolution.
|
|
88
|
+
checking and dependency resolution. Warns if `appVersion` in Chart.yaml does not start
|
|
89
|
+
with `v` (bypass with `--ignore-appv`).
|
|
89
90
|
```bash
|
|
90
|
-
chart-to-registry --chart <dir> [--project <id>] [--repository <name>] [--force] [--dry-run]
|
|
91
|
+
chart-to-registry --chart <dir> [--project <id>] [--repository <name>] [--force] [--dry-run] [--ignore-appv]
|
|
91
92
|
```
|
|
92
93
|
|
|
93
94
|
**`push-my-chart`** — Publishes Helm charts to both GCS Chartmuseum (legacy) and
|
|
94
|
-
GCP Artifact Registry.
|
|
95
|
+
GCP Artifact Registry. Displays chart name, version, and appVersion before pushing;
|
|
96
|
+
warns if `appVersion` does not start with `v`.
|
|
95
97
|
```bash
|
|
96
98
|
push-my-chart [--chart <dir>] [--no-registry]
|
|
97
99
|
```
|
|
@@ -226,11 +228,20 @@ Manager, updating `.npmrc` / `.yarnrc.yml`. Skips refresh if the token is less t
|
|
|
226
228
|
refresh-npm-token [--project <id>] [--force] [--dry-run]
|
|
227
229
|
```
|
|
228
230
|
|
|
229
|
-
**`
|
|
230
|
-
|
|
231
|
+
**`repo-panopticon`** — Multi-repo health dashboard. Checks each repository against a suite
|
|
232
|
+
of indicators in a compact 80-column table. Monorepo-aware: workspace packages are
|
|
233
|
+
auto-discovered, grouped under their parent repo label, and checked individually.
|
|
231
234
|
```bash
|
|
232
|
-
|
|
235
|
+
repo-panopticon <dirs...> # check one or more repos / globs
|
|
236
|
+
repo-panopticon @plt/* @kud/* @srv/* # typical full-org sweep
|
|
237
|
+
repo-panopticon --no-pull @plt/* # skip git pull/fetch
|
|
238
|
+
repo-panopticon --limit 40 @srv/* # cap displayed rows (default 80)
|
|
239
|
+
repo-panopticon --concurrency 10 @srv/* # increase parallelism (default 5)
|
|
233
240
|
```
|
|
241
|
+
Columns: `mgr` (npm/yrn) · `pkg` (package.json version) · `git` (latest semver tag) ·
|
|
242
|
+
`scop` (package scope) · `cir` (CircleCI — ✘ missing · L legacy · 1 orb v1 · 2 orb v2) ·
|
|
243
|
+
`ESM` (`"type":"module"`) · `har` (.har hooks directory) · `lnt` (ESLint major version) ·
|
|
244
|
+
`bbl` (Babel presence). Repo name shown in orange when dirty or sync failed.
|
|
234
245
|
|
|
235
246
|
**`pkglint`** — Verifies required documentation files (README.md, LICENSE.md) are
|
|
236
247
|
present before publishing.
|
|
@@ -252,8 +263,9 @@ count-lines-of-code [--src-only]
|
|
|
252
263
|
|
|
253
264
|
### CI/CD Integration
|
|
254
265
|
|
|
255
|
-
**`circle-orb-it`** — Installs or updates CircleCI configuration (Leverege orb) and
|
|
256
|
-
GitHub Dependabot settings for a repository.
|
|
266
|
+
**`circle-orb-it`** — Installs or updates CircleCI configuration (Leverege orb v2.0.0) and
|
|
267
|
+
GitHub Dependabot settings for a repository. Auto-detects npm vs yarn-berry and writes
|
|
268
|
+
the appropriate template. Preserves existing customizations.
|
|
257
269
|
```bash
|
|
258
270
|
circle-orb-it [--dry-run] [--debug]
|
|
259
271
|
```
|
|
@@ -340,6 +352,7 @@ BUILD_TOOLS_DEBUG=1 docker-to-registry
|
|
|
340
352
|
|
|
341
353
|
| Tool | Replacement |
|
|
342
354
|
|------|-------------|
|
|
355
|
+
| `pkgck` | `repo-panopticon` |
|
|
343
356
|
| `docker-to-registry-py` | `docker-to-registry` |
|
|
344
357
|
| `chart-to-museum` | `push-my-chart` |
|
|
345
358
|
| `refresh-py-idx` | No longer needed |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leverege/build-tools",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.103.0",
|
|
4
4
|
"description": "A collection of build / support tools for Leverege developers",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"repository": {
|
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
"prepack": "npm exec prepack"
|
|
17
17
|
},
|
|
18
18
|
"bin": {
|
|
19
|
-
"build-and-push": "src/build-and-push.sh",
|
|
20
19
|
"build-cnpg-image": "src/build-cnpg-image.mjs",
|
|
21
20
|
"build-tools": "src/build-tools.mjs",
|
|
22
21
|
"change-up": "src/change-up.sh",
|
|
@@ -29,8 +28,6 @@
|
|
|
29
28
|
"decrypt-secrets": "src/decrypt-secrets.sh",
|
|
30
29
|
"dirty-git": "src/dirty-git.sh",
|
|
31
30
|
"docker-to-registry": "src/docker-to-registry.mjs",
|
|
32
|
-
"docker-to-registry-py": "src/docker-to-registry-py.mjs",
|
|
33
|
-
"docker-to-registry.sh": "src/docker-to-registry.sh",
|
|
34
31
|
"dockreate": "src/dockreate.sh",
|
|
35
32
|
"encrypt-secrets": "src/encrypt-secrets.sh",
|
|
36
33
|
"firebaseDeploy": "src/firebaseDeploy.mjs",
|
|
@@ -55,7 +52,7 @@
|
|
|
55
52
|
"npm-link": "src/npm-link.sh",
|
|
56
53
|
"npm-unlink": "src/npm-unlink.sh",
|
|
57
54
|
"overwhelm": "src/overwhelm.mjs",
|
|
58
|
-
"pkgck": "
|
|
55
|
+
"pkgck": "echo '*** Use repo-panopticon instead! ***",
|
|
59
56
|
"pkglint": "src/pkglint.sh",
|
|
60
57
|
"prepack": "src/prepack.mjs",
|
|
61
58
|
"prune-git": "src/prune-git.sh",
|
|
@@ -65,18 +62,18 @@
|
|
|
65
62
|
"push-my-chart": "src/push-my-chart.mjs",
|
|
66
63
|
"refresh-npm-token": "src/refresh-npm-token.mjs",
|
|
67
64
|
"refresh-py-idx": "src/refresh-py-idx.sh",
|
|
65
|
+
"repo-panopticon": "src/repo-panopticon.mjs",
|
|
68
66
|
"send-to-slack": "src/send-to-slack.mjs",
|
|
69
67
|
"service-man": "src/service-man/service-man.mjs",
|
|
70
68
|
"snapshot-cnpg": "src/snapshot-cnpg.mjs",
|
|
71
|
-
"tag-release": "src/tag-release.mjs",
|
|
72
69
|
"unleash": "src/unleash.mjs",
|
|
73
70
|
"upload-model": "src/upload-model.sh",
|
|
74
71
|
"yarn-build": "src/yarn-build.mjs"
|
|
75
72
|
},
|
|
76
73
|
"author": "Leverege Devs",
|
|
77
74
|
"engines": {
|
|
78
|
-
"node": "^22.11 ||
|
|
79
|
-
"npm": ">=11.6"
|
|
75
|
+
"node": "^22.11 || >=24",
|
|
76
|
+
"npm": ">=10.2 || >=11.6"
|
|
80
77
|
},
|
|
81
78
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
82
79
|
"dependencies": {
|
|
@@ -94,7 +91,7 @@
|
|
|
94
91
|
"googleapis": "^171.4.0",
|
|
95
92
|
"handlebars": "^4.7.9",
|
|
96
93
|
"ignore": "^7.0.5",
|
|
97
|
-
"inquirer": "^13.4.
|
|
94
|
+
"inquirer": "^13.4.3",
|
|
98
95
|
"js-yaml": "^4.1.1",
|
|
99
96
|
"jsdoc": "^4.0.5",
|
|
100
97
|
"ms": "^2.1.3",
|
|
@@ -103,20 +100,20 @@
|
|
|
103
100
|
"p-limit": "^7.3.0",
|
|
104
101
|
"package-up": "^5.0.0",
|
|
105
102
|
"readline-sync": "^1.4.10",
|
|
106
|
-
"semver": "^7.
|
|
103
|
+
"semver": "^7.8.0",
|
|
107
104
|
"shell-quote": "^1.8.3",
|
|
108
105
|
"simple-git": "^3.36.0",
|
|
109
106
|
"sloc": "^0.3.2",
|
|
110
107
|
"superstruct": "^2.0.2",
|
|
111
108
|
"tmp": "^0.2.5",
|
|
112
|
-
"toml": "^
|
|
113
|
-
"yaml": "^2.
|
|
109
|
+
"toml": "^4.1.1",
|
|
110
|
+
"yaml": "^2.9.0",
|
|
114
111
|
"zx": "^8.8.5"
|
|
115
112
|
},
|
|
116
113
|
"devDependencies": {
|
|
117
114
|
"@leverege/eslint-config-leverege": "^5.1.2",
|
|
118
115
|
"chai": "^6.2.2",
|
|
119
116
|
"mocha": "^11.7.5",
|
|
120
|
-
"npm": "^11.
|
|
117
|
+
"npm": "^11.14.1"
|
|
121
118
|
}
|
|
122
119
|
}
|
package/src/Utils.mjs
CHANGED
|
@@ -534,6 +534,7 @@ export const parseHelmChart = async ( helmroot = './helm' ) => {
|
|
|
534
534
|
const chartName = chartYaml.name
|
|
535
535
|
const chartLibrary = chartYaml?.type === 'library'
|
|
536
536
|
const chartVersion = chartYaml.version
|
|
537
|
+
const chartAppVersion = chartYaml.appVersion
|
|
537
538
|
const chartPackage = `${chartName}-${chartVersion}.tgz`
|
|
538
539
|
const valuesText = fs.readFileSync( `${helmroot}/values.yaml`, 'utf8' )
|
|
539
540
|
const valuesYaml = YAML.load( valuesText )
|
|
@@ -573,6 +574,7 @@ export const parseHelmChart = async ( helmroot = './helm' ) => {
|
|
|
573
574
|
chartLibrary,
|
|
574
575
|
chartPackage,
|
|
575
576
|
chartVersion,
|
|
577
|
+
chartAppVersion,
|
|
576
578
|
hasDependencies,
|
|
577
579
|
depsOutOfSync,
|
|
578
580
|
imageRegistry,
|
|
@@ -14,9 +14,10 @@ import {
|
|
|
14
14
|
errorExit,
|
|
15
15
|
establishOauthAccess,
|
|
16
16
|
log,
|
|
17
|
-
warning,
|
|
18
17
|
parseHelmChart,
|
|
18
|
+
proceed,
|
|
19
19
|
shellCmd,
|
|
20
|
+
warning,
|
|
20
21
|
} from './Utils.mjs'
|
|
21
22
|
|
|
22
23
|
const commandLineOptions = [ // Use commandLineOptions to tie into the Usage statements
|
|
@@ -46,6 +47,11 @@ const commandLineOptions = [ // Use commandLineOptions to tie into the Usage sta
|
|
|
46
47
|
type : Boolean,
|
|
47
48
|
description : '{green update artifact registry auth token and exit}',
|
|
48
49
|
},
|
|
50
|
+
{
|
|
51
|
+
name : 'ignore-appv',
|
|
52
|
+
type : Boolean,
|
|
53
|
+
description : '{green ignore the Char.yaml appVersion format}',
|
|
54
|
+
},
|
|
49
55
|
{
|
|
50
56
|
name : 'dry-run',
|
|
51
57
|
type : Boolean,
|
|
@@ -98,6 +104,11 @@ if ( semverLt( process.version, minNodejsVersion ) ) {
|
|
|
98
104
|
// load up all we need to know about the chart
|
|
99
105
|
const helmChart = await parseHelmChart( args.chart )
|
|
100
106
|
|
|
107
|
+
if ( !helmChart.chartAppVersion.startsWith( 'v' ) && !args.ignoreAppv ) {
|
|
108
|
+
warning( 'Chart.yaml appVersion does not begin with the letter "v"\n' )
|
|
109
|
+
await proceed( 'You sure about this?' )
|
|
110
|
+
}
|
|
111
|
+
|
|
101
112
|
// helm push helm-package.tgz oci://REGISTRY_LOCATION/REGISTRY_PROJECT/REGISTRY_REPOSITORY/charts
|
|
102
113
|
//
|
|
103
114
|
const registryLocation = args.location || helmChart.registryLocation
|
|
@@ -1,25 +1,28 @@
|
|
|
1
1
|
version: 2.1
|
|
2
2
|
|
|
3
3
|
orbs:
|
|
4
|
-
leverege: leverege/circle-nodejs-ci@
|
|
4
|
+
leverege: leverege/circle-nodejs-ci@2.0.0
|
|
5
5
|
|
|
6
6
|
workflows:
|
|
7
7
|
build-and-verify:
|
|
8
8
|
jobs:
|
|
9
9
|
- leverege/install-dependencies:
|
|
10
10
|
context: npm
|
|
11
|
-
# pkg-manager: yarn-berry # uncomment for Yarn Berry repos
|
|
12
11
|
- leverege/code-analysis:
|
|
12
|
+
context: npm
|
|
13
13
|
requires:
|
|
14
14
|
- leverege/install-dependencies
|
|
15
15
|
- leverege/code-coverage:
|
|
16
|
+
context:
|
|
17
|
+
- npm
|
|
18
|
+
- leverege-gcp # provides GCP_SA_KEY_JSON for tests that need GCP access
|
|
16
19
|
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
20
|
enable-redis: false # set to true if tests require a live Redis connection
|
|
19
21
|
cache-config: '{"type":"inMemory"}' # sets CACHE_CONFIG env var - options: inMemory, redis
|
|
20
22
|
requires:
|
|
21
23
|
- leverege/install-dependencies
|
|
22
24
|
- leverege/generate-docs:
|
|
25
|
+
context: npm
|
|
23
26
|
requires:
|
|
24
27
|
- leverege/install-dependencies
|
|
25
28
|
- leverege/mirror-to-github:
|
|
@@ -11,14 +11,19 @@ import {
|
|
|
11
11
|
enableDebug,
|
|
12
12
|
errorExit,
|
|
13
13
|
getGitRootDirectory,
|
|
14
|
+
getIsYarnProject,
|
|
14
15
|
log,
|
|
16
|
+
mkdirSafe,
|
|
15
17
|
proceed,
|
|
18
|
+
warning,
|
|
16
19
|
} from '../Utils.mjs'
|
|
17
20
|
|
|
18
|
-
const ORB_VERSION = '
|
|
21
|
+
const ORB_VERSION = '2.0.0' // Keep this in sync with the versions in circle-npm.yml and circle-yarn.yml
|
|
19
22
|
const CIRCLE_DIR = '.circleci'
|
|
20
23
|
const CIRCLE_YML = `${CIRCLE_DIR}/config.yml`
|
|
21
|
-
const
|
|
24
|
+
const CIRCLE_NPM_TEMPLATE = path.join( BUILD_TOOLS_ROOT, 'circle-orb-it', 'circle-npm.yml' )
|
|
25
|
+
const CIRCLE_YARN_TEMPLATE = path.join( BUILD_TOOLS_ROOT, 'circle-orb-it', 'circle-yarn.yml' )
|
|
26
|
+
const CIRCLE_YML_TEMPLATE = isYarn => ( isYarn ? CIRCLE_YARN_TEMPLATE : CIRCLE_NPM_TEMPLATE )
|
|
22
27
|
const GITHUB_DIR = '.github'
|
|
23
28
|
const DEPENDABOT_FILE = `${GITHUB_DIR}/dependabot.yml`
|
|
24
29
|
const DEPENDABOT_TEMPLATE = path.join( BUILD_TOOLS_ROOT, 'circle-orb-it', 'dependabot.yml' )
|
|
@@ -52,9 +57,13 @@ if ( !gitRoot ) {
|
|
|
52
57
|
errorExit( 'This command must be run inside a git repository' )
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
const isYarn = getIsYarnProject( gitRoot )
|
|
61
|
+
const circleTemplate = CIRCLE_YML_TEMPLATE( isYarn )
|
|
62
|
+
log( chalk.blue( `\nPackage manager: ${isYarn ? 'yarn-berry' : 'npm'}` ) )
|
|
63
|
+
|
|
55
64
|
// Warn if this looks like a Python project since the orb is Node.js only
|
|
56
65
|
if ( fs.existsSync( path.join( gitRoot, 'pyproject.toml' ) ) ) {
|
|
57
|
-
|
|
66
|
+
warning( 'pyproject.toml detected - the leverege orb is Node.js only' )
|
|
58
67
|
await proceed( 'Continue anyway?' )
|
|
59
68
|
}
|
|
60
69
|
|
|
@@ -119,7 +128,7 @@ if ( !writeCircleYml && !writeDependabot ) {
|
|
|
119
128
|
|
|
120
129
|
if ( options.dryRun ) {
|
|
121
130
|
if ( writeCircleYml ) {
|
|
122
|
-
let dryRunContent = fs.readFileSync(
|
|
131
|
+
let dryRunContent = fs.readFileSync( circleTemplate, 'utf-8' )
|
|
123
132
|
if ( redisEnabled ) {
|
|
124
133
|
dryRunContent = dryRunContent
|
|
125
134
|
.replace( 'enable-redis: false', 'enable-redis: true' )
|
|
@@ -142,10 +151,8 @@ if ( options.dryRun ) {
|
|
|
142
151
|
// ---------- Write ----------
|
|
143
152
|
|
|
144
153
|
if ( writeCircleYml ) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
let circleYmlContent = fs.readFileSync( CIRCLE_YML_TEMPLATE, 'utf-8' )
|
|
154
|
+
mkdirSafe( circleDir )
|
|
155
|
+
let circleYmlContent = fs.readFileSync( circleTemplate, 'utf-8' )
|
|
149
156
|
if ( redisEnabled ) {
|
|
150
157
|
circleYmlContent = circleYmlContent
|
|
151
158
|
.replace( 'enable-redis: false', 'enable-redis: true' )
|
|
@@ -162,9 +169,7 @@ if ( writeCircleYml ) {
|
|
|
162
169
|
}
|
|
163
170
|
|
|
164
171
|
if ( writeDependabot ) {
|
|
165
|
-
|
|
166
|
-
fs.mkdirSync( githubDir, { recursive : true } )
|
|
167
|
-
}
|
|
172
|
+
mkdirSafe( githubDir )
|
|
168
173
|
fs.writeFileSync( dependabotPath, fs.readFileSync( DEPENDABOT_TEMPLATE, 'utf-8' ) )
|
|
169
174
|
log( chalk.green( `Dependabot config written => ${dependabotPath}` ) )
|
|
170
175
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
version: 2.1
|
|
2
|
+
|
|
3
|
+
orbs:
|
|
4
|
+
leverege: leverege/circle-nodejs-ci@2.0.0
|
|
5
|
+
|
|
6
|
+
workflows:
|
|
7
|
+
build-and-verify:
|
|
8
|
+
jobs:
|
|
9
|
+
- leverege/code-analysis:
|
|
10
|
+
context: npm
|
|
11
|
+
pkg-manager: yarn-berry
|
|
12
|
+
- leverege/code-coverage:
|
|
13
|
+
context:
|
|
14
|
+
- npm
|
|
15
|
+
- leverege-gcp # provides GCP_SA_KEY_JSON for tests that need GCP access
|
|
16
|
+
pkg-manager: yarn-berry
|
|
17
|
+
coverage-threshold: 70 # percentage threshold for functions, lines, and statements
|
|
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
|
+
- leverege/generate-docs:
|
|
21
|
+
context: npm
|
|
22
|
+
pkg-manager: yarn-berry
|
|
23
|
+
- leverege/mirror-to-github:
|
|
24
|
+
context: github-mirror
|
|
25
|
+
# github-team: platform # available teams: platform, utilities, pitcrew, recovr
|
|
@@ -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
|
}
|