@saptools/cf-events 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +232 -58
- package/dist/cli.js +137 -109
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +15 -7
- package/dist/index.js +134 -80
- package/dist/index.js.map +1 -1
- package/package.json +11 -12
package/dist/cli.js
CHANGED
|
@@ -5,10 +5,6 @@ import { readFileSync, realpathSync } from "fs";
|
|
|
5
5
|
import { dirname, join as join2, resolve } from "path";
|
|
6
6
|
import process2 from "process";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
|
-
import {
|
|
9
|
-
formatCurrentCfAppSelector,
|
|
10
|
-
readCurrentCfTarget
|
|
11
|
-
} from "@saptools/cf-sync";
|
|
12
8
|
import { Command } from "commander";
|
|
13
9
|
|
|
14
10
|
// src/types.ts
|
|
@@ -397,6 +393,110 @@ import { execFile } from "child_process";
|
|
|
397
393
|
import { mkdtemp, rm } from "fs/promises";
|
|
398
394
|
import { tmpdir } from "os";
|
|
399
395
|
import { join } from "path";
|
|
396
|
+
import { promisify } from "util";
|
|
397
|
+
var execFileAsync = promisify(execFile);
|
|
398
|
+
var REGION_API_MAP = {
|
|
399
|
+
ae01: "https://api.cf.ae01.hana.ondemand.com",
|
|
400
|
+
ap01: "https://api.cf.ap01.hana.ondemand.com",
|
|
401
|
+
ap10: "https://api.cf.ap10.hana.ondemand.com",
|
|
402
|
+
ap11: "https://api.cf.ap11.hana.ondemand.com",
|
|
403
|
+
ap12: "https://api.cf.ap12.hana.ondemand.com",
|
|
404
|
+
ap20: "https://api.cf.ap20.hana.ondemand.com",
|
|
405
|
+
ap21: "https://api.cf.ap21.hana.ondemand.com",
|
|
406
|
+
ap30: "https://api.cf.ap30.hana.ondemand.com",
|
|
407
|
+
br10: "https://api.cf.br10.hana.ondemand.com",
|
|
408
|
+
br20: "https://api.cf.br20.hana.ondemand.com",
|
|
409
|
+
br30: "https://api.cf.br30.hana.ondemand.com",
|
|
410
|
+
ca10: "https://api.cf.ca10.hana.ondemand.com",
|
|
411
|
+
ca20: "https://api.cf.ca20.hana.ondemand.com",
|
|
412
|
+
ch20: "https://api.cf.ch20.hana.ondemand.com",
|
|
413
|
+
cn20: "https://api.cf.cn20.platform.sapcloud.cn",
|
|
414
|
+
cn40: "https://api.cf.cn40.platform.sapcloud.cn",
|
|
415
|
+
eu01: "https://api.cf.eu01.hana.ondemand.com",
|
|
416
|
+
eu02: "https://api.cf.eu02.hana.ondemand.com",
|
|
417
|
+
eu10: "https://api.cf.eu10.hana.ondemand.com",
|
|
418
|
+
"eu10-002": "https://api.cf.eu10-002.hana.ondemand.com",
|
|
419
|
+
"eu10-003": "https://api.cf.eu10-003.hana.ondemand.com",
|
|
420
|
+
"eu10-004": "https://api.cf.eu10-004.hana.ondemand.com",
|
|
421
|
+
"eu10-005": "https://api.cf.eu10-005.hana.ondemand.com",
|
|
422
|
+
"eu10-006": "https://api.cf.eu10-006.hana.ondemand.com",
|
|
423
|
+
eu11: "https://api.cf.eu11.hana.ondemand.com",
|
|
424
|
+
eu13: "https://api.cf.eu13.hana.ondemand.com",
|
|
425
|
+
eu20: "https://api.cf.eu20.hana.ondemand.com",
|
|
426
|
+
"eu20-001": "https://api.cf.eu20-001.hana.ondemand.com",
|
|
427
|
+
eu30: "https://api.cf.eu30.hana.ondemand.com",
|
|
428
|
+
in30: "https://api.cf.in30.hana.ondemand.com",
|
|
429
|
+
jp10: "https://api.cf.jp10.hana.ondemand.com",
|
|
430
|
+
jp20: "https://api.cf.jp20.hana.ondemand.com",
|
|
431
|
+
jp30: "https://api.cf.jp30.hana.ondemand.com",
|
|
432
|
+
jp31: "https://api.cf.jp31.hana.ondemand.com",
|
|
433
|
+
sa30: "https://api.cf.sa30.hana.ondemand.com",
|
|
434
|
+
us10: "https://api.cf.us10.hana.ondemand.com",
|
|
435
|
+
"us10-001": "https://api.cf.us10-001.hana.ondemand.com",
|
|
436
|
+
"us10-002": "https://api.cf.us10-002.hana.ondemand.com",
|
|
437
|
+
us20: "https://api.cf.us20.hana.ondemand.com",
|
|
438
|
+
us21: "https://api.cf.us21.hana.ondemand.com",
|
|
439
|
+
us30: "https://api.cf.us30.hana.ondemand.com"
|
|
440
|
+
// add more if needed, this covers common SAP BTP regions
|
|
441
|
+
};
|
|
442
|
+
function getApiEndpointForRegion(regionKey) {
|
|
443
|
+
return REGION_API_MAP[regionKey];
|
|
444
|
+
}
|
|
445
|
+
function getRegionKeyForApi(apiEndpoint) {
|
|
446
|
+
const normalized = apiEndpoint.trim().replace(/\/+$/, "").toLowerCase();
|
|
447
|
+
for (const [key, api] of Object.entries(REGION_API_MAP)) {
|
|
448
|
+
if (api.toLowerCase() === normalized) {
|
|
449
|
+
return key;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return void 0;
|
|
453
|
+
}
|
|
454
|
+
async function readCurrentCfTarget() {
|
|
455
|
+
const cfBin = process.env["CF_EVENTS_CF_BIN"] ?? "cf";
|
|
456
|
+
const isScript = cfBin.endsWith(".mjs") || cfBin.endsWith(".js");
|
|
457
|
+
const file = isScript ? "node" : cfBin;
|
|
458
|
+
const args = isScript ? [cfBin, "target"] : ["target"];
|
|
459
|
+
try {
|
|
460
|
+
const { stdout } = await execFileAsync(file, args, {
|
|
461
|
+
maxBuffer: 1024 * 1024,
|
|
462
|
+
timeout: 1e4
|
|
463
|
+
});
|
|
464
|
+
return parseCfTargetOutput(stdout);
|
|
465
|
+
} catch {
|
|
466
|
+
return void 0;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function parseCfTargetOutput(stdout) {
|
|
470
|
+
const fields = parseCfTargetFields(stdout);
|
|
471
|
+
const apiEndpoint = fields.get("api endpoint");
|
|
472
|
+
const orgName = fields.get("org");
|
|
473
|
+
const spaceName = fields.get("space");
|
|
474
|
+
if (!apiEndpoint || !orgName || !spaceName) {
|
|
475
|
+
return void 0;
|
|
476
|
+
}
|
|
477
|
+
const regionKey = getRegionKeyForApi(apiEndpoint);
|
|
478
|
+
return {
|
|
479
|
+
apiEndpoint,
|
|
480
|
+
orgName,
|
|
481
|
+
spaceName,
|
|
482
|
+
...regionKey ? { regionKey } : {}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function parseCfTargetFields(stdout) {
|
|
486
|
+
const fields = /* @__PURE__ */ new Map();
|
|
487
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
488
|
+
const sep = line.indexOf(":");
|
|
489
|
+
if (sep < 0) {
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
const key = line.slice(0, sep).trim().toLowerCase();
|
|
493
|
+
const value = line.slice(sep + 1).trim();
|
|
494
|
+
if (key && value) {
|
|
495
|
+
fields.set(key, value);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return fields;
|
|
499
|
+
}
|
|
400
500
|
var CF_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
401
501
|
var CF_COMMAND_TIMEOUT_MS = 3e4;
|
|
402
502
|
var CF_RETRY_MAX_ATTEMPTS = 3;
|
|
@@ -526,7 +626,6 @@ function extractSafeCliDetail(error) {
|
|
|
526
626
|
}
|
|
527
627
|
|
|
528
628
|
// src/selector.ts
|
|
529
|
-
import { readStructure } from "@saptools/cf-sync";
|
|
530
629
|
var SELECTOR_USAGE = 'use "region/org/space/app" or a bare app name';
|
|
531
630
|
function parseSelector(raw) {
|
|
532
631
|
const trimmed = raw.trim();
|
|
@@ -550,92 +649,39 @@ function parseSelector(raw) {
|
|
|
550
649
|
}
|
|
551
650
|
throw new Error(`Invalid selector "${raw}": ${SELECTOR_USAGE}.`);
|
|
552
651
|
}
|
|
553
|
-
function
|
|
554
|
-
const
|
|
555
|
-
if (
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
if (app === void 0) {
|
|
652
|
+
async function resolveSelector(raw) {
|
|
653
|
+
const parsed = parseSelector(raw);
|
|
654
|
+
if (parsed.kind === "appName") {
|
|
655
|
+
const current = await readCurrentCfTarget();
|
|
656
|
+
if (!current) {
|
|
657
|
+
throw new Error(
|
|
658
|
+
"No current CF target found. Run `cf target -o <org> -s <space>` or pass a full region/org/space/app selector."
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
return {
|
|
662
|
+
raw,
|
|
663
|
+
regionKey: current.regionKey ?? "",
|
|
664
|
+
apiEndpoint: current.apiEndpoint,
|
|
665
|
+
orgName: current.orgName,
|
|
666
|
+
spaceName: current.spaceName,
|
|
667
|
+
appName: parsed.appName
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
const apiEndpoint = getApiEndpointForRegion(parsed.regionKey);
|
|
671
|
+
if (!apiEndpoint) {
|
|
574
672
|
throw new Error(
|
|
575
|
-
`
|
|
673
|
+
`Unknown region "${parsed.regionKey}". Use a bare app name (requires current CF target) or a known region key.`
|
|
576
674
|
);
|
|
577
675
|
}
|
|
578
676
|
return {
|
|
579
677
|
raw,
|
|
580
|
-
regionKey:
|
|
581
|
-
apiEndpoint
|
|
582
|
-
orgName:
|
|
583
|
-
spaceName:
|
|
584
|
-
appName:
|
|
678
|
+
regionKey: parsed.regionKey,
|
|
679
|
+
apiEndpoint,
|
|
680
|
+
orgName: parsed.orgName,
|
|
681
|
+
spaceName: parsed.spaceName,
|
|
682
|
+
appName: parsed.appName
|
|
585
683
|
};
|
|
586
684
|
}
|
|
587
|
-
function resolveByAppName(raw, appName, structure) {
|
|
588
|
-
const matches = [];
|
|
589
|
-
for (const region of structure.regions) {
|
|
590
|
-
for (const org of region.orgs) {
|
|
591
|
-
for (const space of org.spaces) {
|
|
592
|
-
for (const app of space.apps) {
|
|
593
|
-
if (app.name === appName) {
|
|
594
|
-
matches.push({
|
|
595
|
-
raw,
|
|
596
|
-
regionKey: region.key,
|
|
597
|
-
apiEndpoint: region.apiEndpoint,
|
|
598
|
-
orgName: org.name,
|
|
599
|
-
spaceName: space.name,
|
|
600
|
-
appName: app.name
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
if (matches.length === 0) {
|
|
608
|
-
throw new Error(
|
|
609
|
-
`App "${appName}" was not found in the CF topology snapshot. Run \`cf-sync sync\` first, or pass a full region/org/space/app selector.`
|
|
610
|
-
);
|
|
611
|
-
}
|
|
612
|
-
if (matches.length > 1) {
|
|
613
|
-
const candidates = matches.map((match) => ` ${match.regionKey}/${match.orgName}/${match.spaceName}/${match.appName}`).join("\n");
|
|
614
|
-
throw new Error(
|
|
615
|
-
`App "${appName}" is ambiguous - it exists in multiple spaces:
|
|
616
|
-
${candidates}
|
|
617
|
-
Pass a full region/org/space/app selector to disambiguate.`
|
|
618
|
-
);
|
|
619
|
-
}
|
|
620
|
-
const onlyMatch = matches[0];
|
|
621
|
-
if (onlyMatch === void 0) {
|
|
622
|
-
throw new Error(`App "${appName}" could not be resolved.`);
|
|
623
|
-
}
|
|
624
|
-
return onlyMatch;
|
|
625
|
-
}
|
|
626
|
-
async function resolveSelector(raw) {
|
|
627
|
-
const parsed = parseSelector(raw);
|
|
628
|
-
const structure = await readStructure();
|
|
629
|
-
if (structure === void 0) {
|
|
630
|
-
throw new Error(
|
|
631
|
-
"No CF topology snapshot found. Run `cf-sync sync` (or `cf-sync space ...`) first."
|
|
632
|
-
);
|
|
633
|
-
}
|
|
634
|
-
if (parsed.kind === "explicit") {
|
|
635
|
-
return resolveExplicit(raw, parsed, structure);
|
|
636
|
-
}
|
|
637
|
-
return resolveByAppName(raw, parsed.appName, structure);
|
|
638
|
-
}
|
|
639
685
|
|
|
640
686
|
// src/ssh.ts
|
|
641
687
|
var SSH_AUTHORIZED_TYPE = "audit.app.ssh-authorized";
|
|
@@ -847,26 +893,8 @@ function parsePositiveInteger(value, optionName) {
|
|
|
847
893
|
function appLabel(selector) {
|
|
848
894
|
return (selector.split("/").at(-1) ?? selector).trim();
|
|
849
895
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
return selector;
|
|
853
|
-
}
|
|
854
|
-
const current = await readCurrentCfTarget(currentCfContext()).catch((error) => {
|
|
855
|
-
throw new Error(
|
|
856
|
-
"No current CF target found. Run `cf target -o <org> -s <space>` or pass a full region/org/space/app selector.",
|
|
857
|
-
{ cause: error }
|
|
858
|
-
);
|
|
859
|
-
});
|
|
860
|
-
if (current === void 0) {
|
|
861
|
-
throw new Error(
|
|
862
|
-
"No current CF target found. Run `cf target -o <org> -s <space>` or pass a full region/org/space/app selector."
|
|
863
|
-
);
|
|
864
|
-
}
|
|
865
|
-
return formatCurrentCfAppSelector(current, selector);
|
|
866
|
-
}
|
|
867
|
-
function currentCfContext() {
|
|
868
|
-
const command = process2.env["CF_EVENTS_CF_BIN"];
|
|
869
|
-
return command === void 0 ? void 0 : { command };
|
|
896
|
+
function resolveSelectorArgument(selector) {
|
|
897
|
+
return selector;
|
|
870
898
|
}
|
|
871
899
|
function writeOut(text) {
|
|
872
900
|
process2.stdout.write(text.endsWith("\n") ? text : `${text}
|
|
@@ -881,7 +909,7 @@ function writeJsonLine(value) {
|
|
|
881
909
|
`);
|
|
882
910
|
}
|
|
883
911
|
async function runEvents(selector, flags) {
|
|
884
|
-
const resolvedSelector =
|
|
912
|
+
const resolvedSelector = resolveSelectorArgument(selector);
|
|
885
913
|
const runtime = new CfEventsRuntime();
|
|
886
914
|
const events = await runtime.fetchEvents(resolvedSelector, buildCredentials(flags), {
|
|
887
915
|
limit: flags.limit ?? DEFAULT_EVENT_LIMIT,
|
|
@@ -895,7 +923,7 @@ async function runEvents(selector, flags) {
|
|
|
895
923
|
writeOut(formatEventsReport(appLabel(resolvedSelector), events, /* @__PURE__ */ new Date()));
|
|
896
924
|
}
|
|
897
925
|
async function runSshStatus(selector, flags) {
|
|
898
|
-
const resolvedSelector =
|
|
926
|
+
const resolvedSelector = resolveSelectorArgument(selector);
|
|
899
927
|
const runtime = new CfEventsRuntime();
|
|
900
928
|
const status = await runtime.getSshStatus(
|
|
901
929
|
resolvedSelector,
|
|
@@ -909,7 +937,7 @@ async function runSshStatus(selector, flags) {
|
|
|
909
937
|
writeOut(formatSshStatusReport(status, /* @__PURE__ */ new Date()));
|
|
910
938
|
}
|
|
911
939
|
async function runCrashes(selector, flags) {
|
|
912
|
-
const resolvedSelector =
|
|
940
|
+
const resolvedSelector = resolveSelectorArgument(selector);
|
|
913
941
|
const runtime = new CfEventsRuntime();
|
|
914
942
|
const summary = await runtime.getCrashes(resolvedSelector, buildCredentials(flags), {
|
|
915
943
|
limit: flags.limit ?? DEFAULT_CRASH_LIMIT,
|
|
@@ -922,7 +950,7 @@ async function runCrashes(selector, flags) {
|
|
|
922
950
|
writeOut(formatCrashReport(summary, /* @__PURE__ */ new Date()));
|
|
923
951
|
}
|
|
924
952
|
async function runStatus(selector, flags) {
|
|
925
|
-
const resolvedSelector =
|
|
953
|
+
const resolvedSelector = resolveSelectorArgument(selector);
|
|
926
954
|
const runtime = new CfEventsRuntime();
|
|
927
955
|
const health = await runtime.getStatus(resolvedSelector, buildCredentials(flags));
|
|
928
956
|
if (flags.json === true) {
|
|
@@ -939,7 +967,7 @@ function emitWatchEvent(event, asJson) {
|
|
|
939
967
|
writeOut(formatEventLine(event, /* @__PURE__ */ new Date()));
|
|
940
968
|
}
|
|
941
969
|
async function runWatch(selector, flags) {
|
|
942
|
-
const resolvedSelector =
|
|
970
|
+
const resolvedSelector = resolveSelectorArgument(selector);
|
|
943
971
|
const interval = flags.interval ?? DEFAULT_WATCH_INTERVAL_MS;
|
|
944
972
|
if (interval < MIN_WATCH_INTERVAL_MS) {
|
|
945
973
|
throw new Error(`--interval must be at least ${MIN_WATCH_INTERVAL_MS.toString()}ms.`);
|