@nuvlore/extension-bds 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +3 -0
  3. package/agents/bds-airflow-job-status-readonly.yaml +123 -0
  4. package/agents/bds-disk-investigator.yaml +112 -0
  5. package/agents/bds-inventory-readonly.yaml +60 -0
  6. package/agents/bds-oncall-reporter.yaml +113 -0
  7. package/agents/bds-prism-service-proxy-timeout-readonly.yaml +105 -0
  8. package/agents/bds-scylla-tenant-bucket-readonly.yaml +104 -0
  9. package/agents/bds-tenant-data-operation-log-readonly.yaml +98 -0
  10. package/agents/bds-yarn-app-health-readonly.yaml +114 -0
  11. package/agents/pharos-alert-investigator.yaml +81 -0
  12. package/package.json +60 -0
  13. package/skills/bds-airflow-job-status-investigation/SKILL.md +104 -0
  14. package/skills/bds-airflow-job-status-investigation/references/airflow-access-runbook.md +35 -0
  15. package/skills/bds-airflow-job-status-investigation/references/airflow-bas-dr-runbook.md +42 -0
  16. package/skills/bds-airflow-job-status-investigation/references/airflow-copy-and-migration-runbook.md +63 -0
  17. package/skills/bds-airflow-job-status-investigation/references/airflow-job-status-runbook.md +15 -0
  18. package/skills/bds-airflow-job-status-investigation/references/airflow-output-runbook.md +28 -0
  19. package/skills/bds-airflow-job-status-investigation/references/airflow-snapshot-runbook.md +38 -0
  20. package/skills/bds-disk-io-alert-investigation/SKILL.md +116 -0
  21. package/skills/bds-hadoop-review/SKILL.md +66 -0
  22. package/skills/bds-machine-inventory/SKILL.md +48 -0
  23. package/skills/bds-oncall-followup/SKILL.md +71 -0
  24. package/skills/bds-oncall-followup/references/oncall-followup-runbook.md +58 -0
  25. package/skills/bds-prism-service-proxy-timeout-investigation/SKILL.md +73 -0
  26. package/skills/bds-prism-service-proxy-timeout-investigation/references/service-proxy-timeout-runbook.md +48 -0
  27. package/skills/bds-scylla-tenant-bucket-investigation/SKILL.md +73 -0
  28. package/skills/bds-scylla-tenant-bucket-investigation/references/tenant-bucket-runbook.md +91 -0
  29. package/skills/bds-tenant-data-operation-log-investigation/SKILL.md +89 -0
  30. package/skills/bds-yarn-app-health-investigation/SKILL.md +77 -0
  31. package/skills/bds-yarn-app-health-investigation/references/yarn-app-health-runbook.md +53 -0
  32. package/skills/pharos-alert-investigation/SKILL.md +80 -0
  33. package/src/bds-atomic-log-tools.mjs +300 -0
  34. package/src/bds-team-membership-claude-output.mjs +36 -0
  35. package/src/bds-team-membership-quality.mjs +90 -0
  36. package/tools/bds_airflow_dag_log_read.mjs +24 -0
  37. package/tools/bds_airflow_job_status_command_pack.mjs +587 -0
  38. package/tools/bds_bas_log_read.mjs +23 -0
  39. package/tools/bds_bdm_httpd_log_read.mjs +24 -0
  40. package/tools/bds_chef_client_log_read.mjs +23 -0
  41. package/tools/bds_dashboard_links.mjs +178 -0
  42. package/tools/bds_dashboard_query_knowledge.mjs +258 -0
  43. package/tools/bds_dashboard_query_read.mjs +120 -0
  44. package/tools/bds_disk_io_alert_command_pack.mjs +287 -0
  45. package/tools/bds_environment_host_lookup.mjs +741 -0
  46. package/tools/bds_hdfs_datanode_log_read.mjs +23 -0
  47. package/tools/bds_hdfs_journalnode_log_read.mjs +23 -0
  48. package/tools/bds_hdfs_namenode_log_read.mjs +23 -0
  49. package/tools/bds_hdfs_path_knowledge.mjs +324 -0
  50. package/tools/bds_information_source_knowledge.mjs +356 -0
  51. package/tools/bds_kms_log_read.mjs +23 -0
  52. package/tools/bds_log_path_knowledge.mjs +267 -0
  53. package/tools/bds_machine_inventory.mjs +434 -0
  54. package/tools/bds_mapreduce_historyserver_log_read.mjs +23 -0
  55. package/tools/bds_migration_service_operation_command_pack.mjs +233 -0
  56. package/tools/bds_oncall_followup_payload_draft.mjs +285 -0
  57. package/tools/bds_pharos_query_pack.mjs +218 -0
  58. package/tools/bds_portal_operation_knowledge.mjs +212 -0
  59. package/tools/bds_prism_clone_status_links.mjs +129 -0
  60. package/tools/bds_prism_service_proxy_timeout_query_pack.mjs +224 -0
  61. package/tools/bds_public_cloud_pod_log_read.mjs +18 -0
  62. package/tools/bds_scylla_tenant_bucket_command_pack.mjs +323 -0
  63. package/tools/bds_service_proxy_log_read.mjs +23 -0
  64. package/tools/bds_spark_historyserver_log_read.mjs +32 -0
  65. package/tools/bds_spas_mt_log_read.mjs +23 -0
  66. package/tools/bds_tenant_data_operation_log_command_pack.mjs +321 -0
  67. package/tools/bds_yarn_app_health_command_pack.mjs +529 -0
  68. package/tools/bds_yarn_nodemanager_log_read.mjs +23 -0
  69. package/tools/bds_yarn_resourcemanager_log_read.mjs +23 -0
  70. package/tools/bds_zookeeper_log_read.mjs +23 -0
  71. package/tools/confluence_handoff_page_draft.mjs +173 -0
  72. package/vendor/dashboard/src/grafana-dashboard-query.mjs +61 -0
  73. package/vendor/extension-read-only-toolkit/LICENSE +202 -0
  74. package/vendor/extension-read-only-toolkit/README.md +29 -0
  75. package/vendor/extension-read-only-toolkit/package.json +46 -0
  76. package/vendor/extension-read-only-toolkit/src/devops-data-paths.mjs +222 -0
  77. package/vendor/extension-read-only-toolkit/src/devops-diagnostics.mjs +440 -0
  78. package/vendor/extension-read-only-toolkit/src/enterprise-api-read.mjs +180 -0
  79. package/vendor/extension-read-only-toolkit/src/read-only-command-pack.mjs +1 -0
  80. package/vendor/extension-read-only-toolkit/src/read-only-plan.mjs +45 -0
  81. package/vendor/extension-read-only-toolkit/src/read-only-shell-policy.d.mts +26 -0
  82. package/vendor/extension-read-only-toolkit/src/read-only-shell-policy.mjs +170 -0
  83. package/vendor/extension-workday-infra-access/LICENSE +202 -0
  84. package/vendor/extension-workday-infra-access/README.md +3 -0
  85. package/vendor/extension-workday-infra-access/agents/workday-infra-access-readonly.yaml +183 -0
  86. package/vendor/extension-workday-infra-access/package.json +49 -0
  87. package/vendor/extension-workday-infra-access/skills/workday-infra-access/SKILL.md +102 -0
  88. package/vendor/extension-workday-infra-access/skills/workday-infra-access/references/access-boundary-runbook.md +43 -0
  89. package/vendor/extension-workday-infra-access/skills/workday-infra-access/references/access-runbook.md +15 -0
  90. package/vendor/extension-workday-infra-access/skills/workday-infra-access/references/access-scylla-cloud-runbook.md +39 -0
  91. package/vendor/extension-workday-infra-access/skills/workday-infra-access/references/access-spc-argo-runbook.md +99 -0
  92. package/vendor/extension-workday-infra-access/src/workday-access-profiles.mjs +194 -0
  93. package/vendor/extension-workday-infra-access/tools/aws_cust_federated_readonly_command_pack.mjs +234 -0
  94. package/vendor/extension-workday-infra-access/tools/gcp_readonly_command_pack.mjs +167 -0
  95. package/vendor/extension-workday-infra-access/tools/public_cloud_access_status.mjs +48 -0
  96. package/vendor/extension-workday-infra-access/tools/workday_boundary_login.mjs +460 -0
  97. package/vendor/extension-workday-infra-access/tools/workday_private_cloud_sudo_readonly_command.mjs +386 -0
  98. package/vendor/extension-workday-infra-access/tools/workday_public_cloud_environment_map.mjs +189 -0
  99. package/vendor/extension-workday-infra-access/tools/workday_public_cloud_kubernetes_read.mjs +297 -0
  100. package/vendor/extension-workday-infra-access/tools/workday_public_cloud_spc_login.mjs +505 -0
  101. package/vendor/extension-workday-infra-access/tools/workday_spc_argo_readonly_command_pack.mjs +215 -0
  102. package/vendor/extension-workday-infra-access/workflows/public-cloud-readonly-investigation.js +99 -0
  103. package/workflows/bds-airflow-job-status-investigation.js +168 -0
  104. package/workflows/bds-disk-io-alert-investigation.js +173 -0
  105. package/workflows/bds-oncall-followup.js +112 -0
  106. package/workflows/bds-prism-service-proxy-timeout-investigation.js +69 -0
  107. package/workflows/bds-scylla-tenant-bucket-investigation.js +139 -0
  108. package/workflows/bds-tenant-data-operation-log-investigation.js +189 -0
  109. package/workflows/bds-yarn-app-health-investigation.js +217 -0
@@ -0,0 +1,741 @@
1
+ export const description = "Find concrete private-cloud BDS hosts from the latest authoritative BDS config repo, returning config JSON paths and role-based host lists such as kube control planes, workers, etcd, NameNodes, and DataNodes.";
2
+
3
+ import { execFile } from "node:child_process";
4
+ import { promises as fs } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { basename, join } from "node:path";
7
+ import { promisify } from "node:util";
8
+ import { z } from "zod";
9
+ import { defineReadOnlyTool, fail, ok } from "@nuvlore/extension-read-only-toolkit/devops-diagnostics";
10
+ import { configuredDataPath, devopsCachePath } from "@nuvlore/extension-read-only-toolkit/devops-data-paths";
11
+
12
+ const execFileAsync = promisify(execFile);
13
+
14
+ const BDS_REPOS = {
15
+ dev: "ssh://git@bitbucket.workday.com:7999/bds/bds-config-dev.git",
16
+ production: "ssh://git@bitbucket.workday.com:7999/bds/bds-config-cust.git",
17
+ "non-production": "ssh://git@bitbucket.workday.com:7999/bds/bds-config-nprd.git"
18
+ };
19
+
20
+ const ENV_ALIASES = new Map([
21
+ ["dev", "dev"],
22
+ ["development", "dev"],
23
+ ["prod", "production"],
24
+ ["production", "production"],
25
+ ["cust", "production"],
26
+ ["customer", "production"],
27
+ ["nprd", "non-production"],
28
+ ["nonprod", "non-production"],
29
+ ["non-prod", "non-production"],
30
+ ["non-production", "non-production"],
31
+ ["implementation", "non-production"],
32
+ ["impl", "non-production"]
33
+ ]);
34
+
35
+ const DEFAULT_ROLES = ["kube_control_planes", "kube_workers", "etcd"];
36
+ const ROLE_PATHS = {
37
+ kube_control_planes: [
38
+ "bds.kubernetes.kube_control_planes",
39
+ "kubernetes.kube_control_planes"
40
+ ],
41
+ control_planes: [
42
+ "bds.kubernetes.kube_control_planes",
43
+ "kubernetes.kube_control_planes"
44
+ ],
45
+ kube_workers: [
46
+ "bds.kubernetes.kube_workers",
47
+ "kubernetes.kube_workers"
48
+ ],
49
+ workers: [
50
+ "bds.kubernetes.kube_workers",
51
+ "kubernetes.kube_workers"
52
+ ],
53
+ etcd: [
54
+ "bds.kubernetes.etcd",
55
+ "bds.kubernetes.etcd_hosts",
56
+ "kubernetes.etcd",
57
+ "kubernetes.etcd_hosts"
58
+ ],
59
+ namenodes: [
60
+ "bds.active_name_node",
61
+ "bds.standby_name_node",
62
+ "bds.namenodes",
63
+ "bds.name_nodes",
64
+ "bds.hdfs.namenodes",
65
+ "bds.hdfs.name_nodes",
66
+ "hdfs.namenodes",
67
+ "hdfs.name_nodes",
68
+ "hdfs/name_nodes",
69
+ "hdfs/namenodes"
70
+ ],
71
+ namenode: [
72
+ "bds.active_name_node",
73
+ "bds.standby_name_node",
74
+ "bds.namenodes",
75
+ "bds.name_nodes",
76
+ "bds.hdfs.namenodes",
77
+ "bds.hdfs.name_nodes",
78
+ "hdfs.namenodes",
79
+ "hdfs.name_nodes",
80
+ "hdfs/name_nodes",
81
+ "hdfs/namenodes"
82
+ ],
83
+ datanodes: [
84
+ "bds.data_nodes",
85
+ "bds.datanodes",
86
+ "bds.data_nodes",
87
+ "bds.hdfs.datanodes",
88
+ "bds.hdfs.data_nodes"
89
+ ],
90
+ datanode: [
91
+ "bds.data_nodes",
92
+ "bds.datanodes",
93
+ "bds.hdfs.datanodes",
94
+ "bds.hdfs.data_nodes"
95
+ ],
96
+ journalnodes: [
97
+ "bds.journal_nodes",
98
+ "bds.journalnodes",
99
+ "bds.hdfs.journal_nodes",
100
+ "bds.hdfs.journalnodes"
101
+ ],
102
+ journalnode: [
103
+ "bds.journal_nodes",
104
+ "bds.journalnodes",
105
+ "bds.hdfs.journal_nodes",
106
+ "bds.hdfs.journalnodes"
107
+ ],
108
+ resourcemanagers: [
109
+ "bds.active_resource_manager_node",
110
+ "bds.standby_resource_manager_node",
111
+ "bds.resource_managers",
112
+ "bds.yarn.resource_managers",
113
+ "bds.yarn.resourcemanagers"
114
+ ],
115
+ resourcemanager: [
116
+ "bds.active_resource_manager_node",
117
+ "bds.standby_resource_manager_node",
118
+ "bds.resource_managers",
119
+ "bds.yarn.resource_managers",
120
+ "bds.yarn.resourcemanagers"
121
+ ],
122
+ nodemanagers: [
123
+ "bds.data_nodes",
124
+ "bds.node_managers",
125
+ "bds.yarn.node_managers",
126
+ "bds.yarn.nodemanagers"
127
+ ],
128
+ nodemanager: [
129
+ "bds.data_nodes",
130
+ "bds.node_managers",
131
+ "bds.yarn.node_managers",
132
+ "bds.yarn.nodemanagers"
133
+ ],
134
+ zookeeper: [
135
+ "bds.zookeeper_nodes"
136
+ ],
137
+ zk: [
138
+ "bds.zookeeper_nodes"
139
+ ],
140
+ kms: [
141
+ "bds.kms_nodes",
142
+ "bds.kms_nodes_cnames"
143
+ ],
144
+ history_server: [
145
+ "bds.history_server_node"
146
+ ],
147
+ mapreduce_history_server: [
148
+ "bds.history_server_node"
149
+ ],
150
+ spark_history_server: [
151
+ "bds.kubernetes.kube_control_planes",
152
+ "kubernetes.kube_control_planes"
153
+ ],
154
+ oozie: [
155
+ "bds.oozie.oozie_nodes",
156
+ "bds.oozie.mysql_node"
157
+ ],
158
+ bdm: [
159
+ "bds.active_bdm",
160
+ "bds.standby_bdm",
161
+ "active_bdm",
162
+ "standby_bdm"
163
+ ],
164
+ airflow: [
165
+ "bds.kubernetes.kube_control_planes",
166
+ "kubernetes.kube_control_planes"
167
+ ],
168
+ bas: [
169
+ "bds.bas.nodes",
170
+ "bds.bas.hosts",
171
+ "bds.admin_nodes",
172
+ "bds.oozie.oozie_nodes"
173
+ ],
174
+ tenant_data_operation: [
175
+ "bds.kubernetes.kube_control_planes",
176
+ "kubernetes.kube_control_planes"
177
+ ],
178
+ tenant_clone: [
179
+ "bds.kubernetes.kube_control_planes",
180
+ "kubernetes.kube_control_planes"
181
+ ],
182
+ tenant_dr: [
183
+ "bds.kubernetes.kube_control_planes",
184
+ "kubernetes.kube_control_planes"
185
+ ],
186
+ dr_pull: [
187
+ "bds.kubernetes.kube_control_planes",
188
+ "kubernetes.kube_control_planes"
189
+ ],
190
+ finalizer: [
191
+ "bds.kubernetes.kube_control_planes",
192
+ "kubernetes.kube_control_planes"
193
+ ],
194
+ service_proxy: [
195
+ "bds.bds_service_proxy.service_proxy_nodes"
196
+ ],
197
+ bds_service_proxy: [
198
+ "bds.bds_service_proxy.service_proxy_nodes"
199
+ ]
200
+ };
201
+
202
+ const SKIP_CLUSTER_BASENAMES = new Set(["bdm_bds_mapping", "bds_master_ldap"]);
203
+
204
+ function bdsConfigCacheRoot() {
205
+ return configuredDataPath(
206
+ "DEVOPS_BDS_CONFIG_CACHE_ROOT",
207
+ "cache.bdsConfig",
208
+ devopsCachePath("bds-config")
209
+ );
210
+ }
211
+
212
+ function normalizeText(value) {
213
+ return typeof value === "string" ? value.trim() : "";
214
+ }
215
+
216
+ function normalizeEnvironment(env) {
217
+ const normalized = normalizeText(env).toLowerCase();
218
+ const resolved = ENV_ALIASES.get(normalized);
219
+ if (!resolved) {
220
+ throw new Error(`Unsupported BDS environment: ${env}. Use dev, production, or non-production.`);
221
+ }
222
+ return resolved;
223
+ }
224
+
225
+ function repoUrlForEnvironment(env) {
226
+ return BDS_REPOS[normalizeEnvironment(env)];
227
+ }
228
+
229
+ function repoDirForEnvironment(cacheRoot, env) {
230
+ return join(cacheRoot, basename(repoUrlForEnvironment(env), ".git"));
231
+ }
232
+
233
+ async function pathExists(path) {
234
+ try {
235
+ await fs.access(path);
236
+ return true;
237
+ } catch (_error) {
238
+ return false;
239
+ }
240
+ }
241
+
242
+ function assertStructuredHitlApproval(input, context) {
243
+ const approval = input.hitlApproval;
244
+ if (!approval || typeof approval !== "object") {
245
+ throw new Error(`${context} requires structured HITL approval with approved, approvedBy, action, target, reason, expectedImpact, and risk.`);
246
+ }
247
+
248
+ const missing = ["approvedBy", "action", "target", "reason", "expectedImpact", "risk"]
249
+ .filter((key) => !normalizeText(approval[key]));
250
+ if (approval.approved !== true || missing.length > 0) {
251
+ throw new Error(`${context} requires approved=true and complete HITL details: ${missing.join(", ") || "approved"}.`);
252
+ }
253
+ }
254
+
255
+ async function git(repoPath, args, input, execFileImpl = execFileAsync) {
256
+ return execFileImpl("git", ["-C", repoPath, ...args], {
257
+ timeout: input.timeoutMs,
258
+ maxBuffer: 1024 * 1024 * 10
259
+ });
260
+ }
261
+
262
+ async function pullLatest(repoPath, input, execFileImpl = execFileAsync) {
263
+ try {
264
+ await git(repoPath, ["pull", "--ff-only"], input, execFileImpl);
265
+ return { repoPath, repoFreshness: "pulled_latest_with_git_pull_ff_only" };
266
+ } catch (error) {
267
+ const message = String(error?.stderr || error?.stdout || error?.message || "");
268
+ if (!/no tracking information|no upstream branch|There is no tracking information/u.test(message)) {
269
+ throw error;
270
+ }
271
+ await git(repoPath, ["fetch", "origin", "master"], input, execFileImpl);
272
+ const treeRoot = await materializeGitTree(repoPath, "origin/master", input, execFileImpl);
273
+ return {
274
+ repoPath: treeRoot,
275
+ sourceRepoPath: repoPath,
276
+ repoFreshness: "fetched_origin_master_snapshot_no_tracking_branch"
277
+ };
278
+ }
279
+ }
280
+
281
+ async function materializeGitTree(repoPath, ref, input, execFileImpl = execFileAsync) {
282
+ const rev = (await git(repoPath, ["rev-parse", "--short=12", ref], input, execFileImpl)).stdout.trim();
283
+ const safeName = basename(repoPath).replace(/[^a-zA-Z0-9._-]+/gu, "_");
284
+ const treeRoot = join(tmpdir(), `eldwin-bds-config-${safeName}-${rev}`);
285
+ if (await pathExists(treeRoot)) return treeRoot;
286
+ await fs.mkdir(treeRoot, { recursive: true });
287
+ const archivePath = join(tmpdir(), `eldwin-bds-config-${safeName}-${rev}.tar`);
288
+ await git(repoPath, ["archive", "--format=tar", `--output=${archivePath}`, ref], input, execFileImpl);
289
+ await execFileImpl("tar", ["-xf", archivePath, "-C", treeRoot], {
290
+ timeout: input.timeoutMs,
291
+ maxBuffer: 1024 * 1024 * 100
292
+ });
293
+ await fs.rm(archivePath, { force: true });
294
+ return treeRoot;
295
+ }
296
+
297
+ async function ensureRepo(env, input, execFileImpl = execFileAsync) {
298
+ const explicitRepoPath = normalizeText(input.repoPath);
299
+ if (explicitRepoPath) {
300
+ if (!(await pathExists(join(explicitRepoPath, ".git")))) {
301
+ throw new Error(`repoPath must be a git checkout so bds_environment_host_lookup can pull latest code before lookup: ${explicitRepoPath}`);
302
+ }
303
+ return pullLatest(explicitRepoPath, input, execFileImpl);
304
+ }
305
+
306
+ const environment = normalizeEnvironment(env);
307
+ const cacheRoot = normalizeText(input.cacheRoot) || bdsConfigCacheRoot();
308
+ const repoPath = repoDirForEnvironment(cacheRoot, environment);
309
+ const repoUrl = repoUrlForEnvironment(environment);
310
+
311
+ if (await pathExists(join(repoPath, ".git"))) {
312
+ return pullLatest(repoPath, input, execFileImpl);
313
+ }
314
+
315
+ if (!input.allowCacheUpdate) {
316
+ throw new Error(`Local BDS config cache is missing at ${repoPath}. Provide repoPath for an existing checkout or set allowCacheUpdate=true to explicitly clone the read-only config repo.`);
317
+ }
318
+ assertStructuredHitlApproval(input, "BDS environment host lookup cache clone");
319
+ await fs.mkdir(cacheRoot, { recursive: true });
320
+ await execFileImpl("git", ["clone", "--depth", "1", repoUrl, repoPath], {
321
+ timeout: input.timeoutMs,
322
+ maxBuffer: 1024 * 1024 * 10
323
+ });
324
+ return { repoPath, repoFreshness: "cloned_latest_readonly_cache" };
325
+ }
326
+
327
+ async function listJsonFiles(root) {
328
+ const files = [];
329
+ if (!(await pathExists(root))) return files;
330
+ const entries = await fs.readdir(root, { withFileTypes: true });
331
+ for (const entry of entries) {
332
+ const path = join(root, entry.name);
333
+ if (entry.isDirectory()) {
334
+ files.push(...await listJsonFiles(path));
335
+ } else if (entry.isFile() && entry.name.endsWith(".json")) {
336
+ files.push(path);
337
+ }
338
+ }
339
+ return files;
340
+ }
341
+
342
+ async function loadClusterDocuments(repoPath) {
343
+ const files = [
344
+ ...await listJsonFiles(join(repoPath, "data_bags", "bds")),
345
+ ...await listJsonFiles(join(repoPath, "config", "bds"))
346
+ ];
347
+ const docs = [];
348
+ for (const filePath of files.sort()) {
349
+ const cluster = basename(filePath, ".json");
350
+ if (SKIP_CLUSTER_BASENAMES.has(cluster) && !["bdm_bds_mapping", "bdm_config"].includes(cluster)) continue;
351
+ try {
352
+ const document = JSON.parse(await fs.readFile(filePath, "utf8"));
353
+ docs.push({ cluster, filePath, document });
354
+ } catch (error) {
355
+ docs.push({ cluster, filePath, parseError: error instanceof Error ? error.message : String(error) });
356
+ }
357
+ }
358
+ return docs;
359
+ }
360
+
361
+ function getPathValue(value, dotPath) {
362
+ return dotPath.split(/[./]+/u).filter(Boolean).reduce((current, part) => {
363
+ if (current === undefined || current === null) return undefined;
364
+ return current[part];
365
+ }, value);
366
+ }
367
+
368
+ function asStringList(value) {
369
+ if (Array.isArray(value)) {
370
+ return value.flatMap((item) => typeof item === "string" ? [item.trim()] : []).filter(Boolean);
371
+ }
372
+ if (typeof value === "string") {
373
+ return value.split(/[,\s]+/u).map((item) => item.trim()).filter(Boolean);
374
+ }
375
+ return [];
376
+ }
377
+
378
+ function normalizeRole(role) {
379
+ return normalizeText(role).replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
380
+ }
381
+
382
+ function pathsForRole(role) {
383
+ const normalized = normalizeRole(role);
384
+ return ROLE_PATHS[normalized] ?? [
385
+ role,
386
+ `bds.${role}`,
387
+ `bds.${normalized}`,
388
+ `bds.${normalized}_nodes`,
389
+ `bds.${normalized}_hosts`,
390
+ `bds.${normalized}_cnames`
391
+ ];
392
+ }
393
+
394
+ function clusterMatches(cluster, query) {
395
+ const raw = normalizeText(query);
396
+ return !raw || cluster.toLowerCase() === raw.toLowerCase() || cluster.toLowerCase().includes(raw.toLowerCase());
397
+ }
398
+
399
+ function bdmHostsFromMappingDocs(docs, cluster) {
400
+ const targetCluster = normalizeText(cluster).toLowerCase();
401
+ if (!targetCluster) return [];
402
+ return docs.flatMap((doc) => {
403
+ if (!["bdm_bds_mapping", "bdm_config"].includes(doc.cluster) || doc.parseError || !doc.document || typeof doc.document !== "object") return [];
404
+ return Object.entries(doc.document)
405
+ .filter(([, value]) => normalizeText(typeof value === "string" ? value : value?.cluster_name).toLowerCase() === targetCluster)
406
+ .map(([host]) => ({ host, path: `${doc.filePath}#${host}` }));
407
+ });
408
+ }
409
+
410
+ function hostEntries(hosts, path, extra = {}) {
411
+ return asStringList(hosts).map((host) => ({ host, path, ...extra }));
412
+ }
413
+
414
+ function extractHdfsNamespaces(document) {
415
+ const bds = document?.bds ?? {};
416
+ const namespaces = [];
417
+ if (Array.isArray(bds.hdfs)) {
418
+ for (const [index, hdfs] of bds.hdfs.entries()) {
419
+ const namespace = normalizeText(hdfs?.namespace) || `hdfs_${index}`;
420
+ namespaces.push({
421
+ namespace,
422
+ activeNameNode: normalizeText(hdfs?.active_name_node),
423
+ standbyNameNode: normalizeText(hdfs?.standby_name_node),
424
+ nameNodes: dedupeHostEntries([
425
+ ...hostEntries(hdfs?.name_nodes, `bds.hdfs.${index}.name_nodes`, { namespace }),
426
+ ...hostEntries(hdfs?.active_name_node, `bds.hdfs.${index}.active_name_node`, { namespace }),
427
+ ...hostEntries(hdfs?.standby_name_node, `bds.hdfs.${index}.standby_name_node`, { namespace })
428
+ ]),
429
+ dataNodes: dedupeHostEntries(hostEntries(hdfs?.data_nodes, `bds.hdfs.${index}.data_nodes`, { namespace }))
430
+ });
431
+ }
432
+ }
433
+ if (namespaces.length === 0 && bds.hdfs && typeof bds.hdfs === "object" && !Array.isArray(bds.hdfs)) {
434
+ const namespace = normalizeText(bds.hdfs.namespace) || "default";
435
+ const nameNodes = [
436
+ ...hostEntries(bds.hdfs.name_nodes ?? bds.hdfs.namenodes, "bds.hdfs.name_nodes", { namespace }),
437
+ ...hostEntries(bds.hdfs.active_name_node, "bds.hdfs.active_name_node", { namespace }),
438
+ ...hostEntries(bds.hdfs.standby_name_node, "bds.hdfs.standby_name_node", { namespace })
439
+ ];
440
+ const dataNodes = hostEntries(bds.hdfs.data_nodes ?? bds.hdfs.datanodes, "bds.hdfs.data_nodes", { namespace });
441
+ if (nameNodes.length || dataNodes.length) {
442
+ namespaces.push({
443
+ namespace,
444
+ activeNameNode: normalizeText(bds.hdfs.active_name_node),
445
+ standbyNameNode: normalizeText(bds.hdfs.standby_name_node),
446
+ nameNodes: dedupeHostEntries(nameNodes),
447
+ dataNodes: dedupeHostEntries(dataNodes)
448
+ });
449
+ }
450
+ }
451
+ if (namespaces.length === 0 && (bds.active_name_node || bds.standby_name_node || bds.data_nodes)) {
452
+ const namespace = normalizeText(bds.namespace) || "default";
453
+ namespaces.push({
454
+ namespace,
455
+ activeNameNode: normalizeText(bds.active_name_node),
456
+ standbyNameNode: normalizeText(bds.standby_name_node),
457
+ nameNodes: dedupeHostEntries([
458
+ ...hostEntries(bds.active_name_node, "bds.active_name_node", { namespace }),
459
+ ...hostEntries(bds.standby_name_node, "bds.standby_name_node", { namespace })
460
+ ]),
461
+ dataNodes: dedupeHostEntries(hostEntries(bds.data_nodes, "bds.data_nodes", { namespace }))
462
+ });
463
+ }
464
+ if (namespaces.length === 0) {
465
+ const hdfs = document?.hdfs ?? {};
466
+ const nameNodes = hostEntries(hdfs.name_nodes ?? hdfs.namenodes, "hdfs.name_nodes", { namespace: "default" });
467
+ const dataNodes = hostEntries(hdfs.data_nodes ?? hdfs.datanodes, "hdfs.data_nodes", { namespace: "default" });
468
+ if (nameNodes.length || dataNodes.length) {
469
+ namespaces.push({
470
+ namespace: "default",
471
+ activeNameNode: "",
472
+ standbyNameNode: "",
473
+ nameNodes: dedupeHostEntries(nameNodes),
474
+ dataNodes: dedupeHostEntries(dataNodes)
475
+ });
476
+ }
477
+ }
478
+ return namespaces;
479
+ }
480
+
481
+ function extractClusterPatterns(doc, docs) {
482
+ const bds = doc.document?.bds ?? {};
483
+ const hdfsNamespaces = extractHdfsNamespaces(doc.document);
484
+ return {
485
+ hdfsNamespaces,
486
+ kubernetes: {
487
+ controlPlanes: dedupeHostEntries(hostEntries(bds.kubernetes?.kube_control_planes ?? doc.document?.kubernetes?.kube_control_planes, "bds.kubernetes.kube_control_planes")),
488
+ workers: dedupeHostEntries(hostEntries(bds.kubernetes?.kube_workers ?? doc.document?.kubernetes?.kube_workers, "bds.kubernetes.kube_workers")),
489
+ etcd: dedupeHostEntries(hostEntries(bds.kubernetes?.etcd_hosts ?? bds.kubernetes?.etcd ?? doc.document?.kubernetes?.etcd_hosts, "bds.kubernetes.etcd_hosts"))
490
+ },
491
+ bdm: dedupeHostEntries([
492
+ ...bdmHostsFromMappingDocs(docs, doc.cluster),
493
+ ...hostEntries(bds.active_bdm, "bds.active_bdm"),
494
+ ...hostEntries(bds.standby_bdm, "bds.standby_bdm"),
495
+ ...hostEntries(doc.document?.active_bdm, "active_bdm"),
496
+ ...hostEntries(doc.document?.standby_bdm, "standby_bdm")
497
+ ]),
498
+ bas: dedupeHostEntries([
499
+ ...hostEntries(bds.bas?.nodes, "bds.bas.nodes"),
500
+ ...hostEntries(bds.bas?.hosts, "bds.bas.hosts"),
501
+ ...hostEntries(bds.admin_nodes, "bds.admin_nodes"),
502
+ ...hostEntries(bds.oozie?.oozie_nodes, "bds.oozie.oozie_nodes", { legacyConfigField: true, serviceRole: "bas_admin_host" })
503
+ ]),
504
+ oozie: dedupeHostEntries(hostEntries(bds.oozie?.oozie_nodes, "bds.oozie.oozie_nodes")),
505
+ kms: dedupeHostEntries([
506
+ ...hostEntries(bds.kms_nodes, "bds.kms_nodes"),
507
+ ...hostEntries(bds.kms_nodes_cnames, "bds.kms_nodes_cnames")
508
+ ]),
509
+ zookeeper: dedupeHostEntries(hostEntries(bds.zookeeper_nodes, "bds.zookeeper_nodes")),
510
+ journalNodes: dedupeHostEntries(hostEntries(bds.journal_nodes, "bds.journal_nodes")),
511
+ resourceManagers: dedupeHostEntries([
512
+ ...hostEntries(bds.active_resource_manager_node, "bds.active_resource_manager_node"),
513
+ ...hostEntries(bds.standby_resource_manager_node, "bds.standby_resource_manager_node")
514
+ ]),
515
+ historyServer: dedupeHostEntries(hostEntries(bds.history_server_node, "bds.history_server_node")),
516
+ sparkHistoryServer: dedupeHostEntries(hostEntries(bds.kubernetes?.kube_control_planes ?? doc.document?.kubernetes?.kube_control_planes, "bds.kubernetes.kube_control_planes", {
517
+ namespace: "bds-k8s-services",
518
+ serviceRole: "spark_history_server_k8s",
519
+ resourceNames: dedupeStrings([
520
+ ...asStringList(bds.spark_history_server_k8s_list),
521
+ ...asStringList(doc.document?.dcs?.map?.((dc) => dc?.spark_history_server_k8s).filter(Boolean))
522
+ ])
523
+ })),
524
+ serviceProxy: dedupeHostEntries(hostEntries(bds.bds_service_proxy?.service_proxy_nodes, "bds.bds_service_proxy.service_proxy_nodes"))
525
+ };
526
+ }
527
+
528
+ function patternHostsForRole(patterns, role) {
529
+ const normalized = normalizeRole(role);
530
+ if (["namenode", "namenodes", "name_node", "name_nodes"].includes(normalized)) {
531
+ return patterns.hdfsNamespaces.flatMap((namespace) => namespace.nameNodes);
532
+ }
533
+ if (["datanode", "datanodes", "data_node", "data_nodes"].includes(normalized)) {
534
+ return patterns.hdfsNamespaces.flatMap((namespace) => namespace.dataNodes);
535
+ }
536
+ if (["nodemanager", "nodemanagers", "node_manager", "node_managers"].includes(normalized)) {
537
+ return patterns.hdfsNamespaces.flatMap((namespace) => namespace.dataNodes);
538
+ }
539
+ if ([
540
+ "kube_control_planes",
541
+ "kube_control_plane",
542
+ "control_planes",
543
+ "airflow",
544
+ "tenant_data_operation",
545
+ "tenant_clone",
546
+ "tenant_dr",
547
+ "dr_pull",
548
+ "finalizer"
549
+ ].includes(normalized)) return patterns.kubernetes.controlPlanes;
550
+ if (["kube_workers", "workers"].includes(normalized)) return patterns.kubernetes.workers;
551
+ if (normalized === "etcd") return patterns.kubernetes.etcd;
552
+ if (normalized === "bdm") return patterns.bdm;
553
+ if (normalized === "bas") return patterns.bas;
554
+ if (normalized === "oozie") return patterns.oozie;
555
+ if (normalized === "kms") return patterns.kms;
556
+ if (["zookeeper", "zk"].includes(normalized)) return patterns.zookeeper;
557
+ if (["journalnode", "journalnodes", "journal_node", "journal_nodes"].includes(normalized)) return patterns.journalNodes;
558
+ if (["resourcemanager", "resourcemanagers", "resource_manager", "resource_managers"].includes(normalized)) return patterns.resourceManagers;
559
+ if (["history_server", "mapreduce_history_server"].includes(normalized)) return patterns.historyServer;
560
+ if (["spark_history_server", "shs"].includes(normalized)) return patterns.sparkHistoryServer;
561
+ if (["service_proxy", "bds_service_proxy"].includes(normalized)) return patterns.serviceProxy;
562
+ return [];
563
+ }
564
+
565
+ function lookupHosts(docs, input) {
566
+ const roles = input.roles.length ? input.roles : DEFAULT_ROLES;
567
+ const requestedPaths = input.hostFields.length ? input.hostFields : roles.flatMap(pathsForRole);
568
+ const matches = [];
569
+ const parseErrors = [];
570
+
571
+ for (const doc of docs) {
572
+ if (doc.parseError) {
573
+ parseErrors.push({ cluster: doc.cluster, filePath: doc.filePath, error: doc.parseError });
574
+ continue;
575
+ }
576
+ if (!clusterMatches(doc.cluster, input.cluster)) continue;
577
+
578
+ const hostsByRole = {};
579
+ const matchedPaths = [];
580
+ const patterns = extractClusterPatterns(doc, docs);
581
+ for (const role of roles) {
582
+ const roleHosts = patternHostsForRole(patterns, role);
583
+ if (roleHosts.length > 0) matchedPaths.push(...roleHosts.map((entry) => entry.path));
584
+ for (const path of input.hostFields.length ? requestedPaths : pathsForRole(role)) {
585
+ const hosts = asStringList(getPathValue(doc.document, path));
586
+ if (hosts.length === 0) continue;
587
+ matchedPaths.push(path);
588
+ roleHosts.push(...hosts.map((host) => ({ host, path })));
589
+ }
590
+ hostsByRole[normalizeRole(role)] = dedupeHostEntries(roleHosts);
591
+ }
592
+
593
+ if (input.hostFields.length) {
594
+ hostsByRole.explicit_fields = dedupeHostEntries(requestedPaths.flatMap((path) =>
595
+ asStringList(getPathValue(doc.document, path)).map((host) => ({ host, path }))
596
+ ));
597
+ }
598
+
599
+ const hostCount = Object.values(hostsByRole).reduce((sum, values) => sum + values.length, 0);
600
+ if (hostCount > 0 || input.includeEmptyMatches) {
601
+ matches.push({
602
+ cluster: doc.cluster,
603
+ filePath: doc.filePath,
604
+ matchedPaths: Array.from(new Set(matchedPaths)).sort(),
605
+ patterns,
606
+ hostsByRole
607
+ });
608
+ }
609
+ }
610
+
611
+ return { matches, parseErrors };
612
+ }
613
+
614
+ function dedupeHostEntries(entries) {
615
+ const seen = new Set();
616
+ const result = [];
617
+ for (const entry of entries) {
618
+ const key = entry.host;
619
+ if (seen.has(key)) continue;
620
+ seen.add(key);
621
+ result.push(entry);
622
+ }
623
+ return result;
624
+ }
625
+
626
+ function dedupeStrings(values) {
627
+ return Array.from(new Set(values.filter(Boolean)));
628
+ }
629
+
630
+ function normalizeInput(input = {}) {
631
+ return {
632
+ environment: normalizeEnvironment(input.environment ?? "production"),
633
+ cluster: normalizeText(input.cluster),
634
+ roles: Array.isArray(input.roles) ? input.roles.map(normalizeText).filter(Boolean) : [],
635
+ hostFields: Array.isArray(input.hostFields) ? input.hostFields.map(normalizeText).filter(Boolean) : [],
636
+ repoPath: normalizeText(input.repoPath),
637
+ cacheRoot: normalizeText(input.cacheRoot) || bdsConfigCacheRoot(),
638
+ allowCacheUpdate: input.allowCacheUpdate === true,
639
+ hitlApproval: input.hitlApproval,
640
+ includeEmptyMatches: input.includeEmptyMatches === true,
641
+ timeoutMs: Number.isInteger(input.timeoutMs) ? Math.max(1000, input.timeoutMs) : 120_000
642
+ };
643
+ }
644
+
645
+ export class BdsEnvironmentHostLookupService {
646
+ constructor(options = {}) {
647
+ this.execFileImpl = options.execFileImpl || execFileAsync;
648
+ this.loadClusterDocuments = options.loadClusterDocuments || loadClusterDocuments;
649
+ }
650
+
651
+ async invoke(input) {
652
+ const normalized = normalizeInput(input);
653
+ const repoInfo = await ensureRepo(normalized.environment, normalized, this.execFileImpl);
654
+ const repoPath = typeof repoInfo === "string" ? repoInfo : repoInfo.repoPath;
655
+ const docs = await this.loadClusterDocuments(repoPath);
656
+ const result = lookupHosts(docs, normalized);
657
+ return JSON.stringify({
658
+ environment: normalized.environment,
659
+ repoPath,
660
+ sourceRepoPath: repoInfo.sourceRepoPath,
661
+ cluster: normalized.cluster,
662
+ roles: normalized.roles.length ? normalized.roles : DEFAULT_ROLES,
663
+ hostFields: normalized.hostFields,
664
+ repoFreshness: repoInfo.repoFreshness ?? "pulled_latest_with_git_pull_ff_only",
665
+ ...result
666
+ }, null, 2);
667
+ }
668
+ }
669
+
670
+ async function bdsEnvironmentHostLookup(input, options = {}) {
671
+ return new BdsEnvironmentHostLookupService(options).invoke(input);
672
+ }
673
+
674
+ export const __test = {
675
+ BdsEnvironmentHostLookupService,
676
+ asStringList,
677
+ bdsEnvironmentHostLookup,
678
+ clusterMatches,
679
+ dedupeHostEntries,
680
+ ensureRepo,
681
+ getPathValue,
682
+ lookupHosts,
683
+ bdmHostsFromMappingDocs,
684
+ extractClusterPatterns,
685
+ extractHdfsNamespaces,
686
+ patternHostsForRole,
687
+ normalizeEnvironment,
688
+ normalizeInput,
689
+ normalizeRole,
690
+ pathsForRole,
691
+ pullLatest,
692
+ repoDirForEnvironment,
693
+ repoUrlForEnvironment
694
+ };
695
+
696
+ const defaultService = new BdsEnvironmentHostLookupService();
697
+
698
+ const sharedTool = defineReadOnlyTool({
699
+ name: "bds_environment_host_lookup",
700
+ description: "Find concrete private-cloud BDS hosts from the latest authoritative BDS config repo, returning config JSON paths and role-based host lists such as kube control planes, workers, etcd, NameNodes, and DataNodes.",
701
+ meta: {
702
+ permissions: { rule: "ask" }
703
+ },
704
+ inputSchema: {
705
+ environment: z.string().trim().min(1).max(64).default("production"),
706
+ cluster: z.string().trim().min(1).max(256).describe("BDS cluster/environment filter, for example BDS_ASH_CUST_AZ1."),
707
+ roles: z.array(z.string().trim().min(1).max(128)).max(32).default(DEFAULT_ROLES).describe("Host roles to resolve, for example kube_control_planes, kube_workers, etcd, namenodes, datanodes."),
708
+ hostFields: z.array(z.string().trim().min(1).max(512)).max(64).default([]).describe("Optional exact JSON dot paths. When provided, these override role path expansion."),
709
+ repoPath: z.string().trim().max(4096).default(""),
710
+ cacheRoot: z.string().trim().max(4096).default(bdsConfigCacheRoot()),
711
+ allowCacheUpdate: z.boolean().default(false).describe("Set true only after HITL approval to create the local read-only BDS config cache when missing."),
712
+ hitlApproval: z.object({
713
+ approved: z.literal(true),
714
+ approvedBy: z.string().trim().min(1).max(256),
715
+ action: z.string().trim().min(1).max(256),
716
+ target: z.string().trim().min(1).max(4096),
717
+ reason: z.string().trim().min(1).max(2000),
718
+ expectedImpact: z.string().trim().min(1).max(2000),
719
+ risk: z.string().trim().min(1).max(2000)
720
+ }).optional().describe("Required when allowCacheUpdate=true because that creates a local cache."),
721
+ includeEmptyMatches: z.boolean().default(false),
722
+ timeoutMs: z.number().int().positive().max(600_000).default(120_000)
723
+ },
724
+ async handler(input) {
725
+ try {
726
+ return ok(await defaultService.invoke(input));
727
+ } catch (error) {
728
+ return fail(error);
729
+ }
730
+ }
731
+ });
732
+
733
+ export default {
734
+ name: sharedTool.name,
735
+ description,
736
+ inputSchema: sharedTool.inputSchema,
737
+ meta: sharedTool.meta,
738
+ annotations: sharedTool.annotations,
739
+ invoke: sharedTool.invoke,
740
+ handler: sharedTool.handler
741
+ };