@cdot65/prisma-airs-cli 5.3.0 → 5.5.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/README.md +3 -1
- package/dist/{chunk-7Z76VCB3.js → chunk-NKAVUBKI.js} +232 -27
- package/dist/cli/index.js +1621 -379
- package/dist/index.d.ts +14 -0
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
- **Unified automation output** — resource reads support `pretty`, `table`, `markdown`, `csv`, `json`, and `yaml`, with pipe-safe stdout; environment deliverables use HTML or Markdown
|
|
26
26
|
- **Complete pagination** — consistent `--limit`, `--offset`, and `--all` traversal with a configurable safety cap
|
|
27
27
|
- **`airs doctor`** — one-command diagnostics for environment, credentials, and API connectivity
|
|
28
|
-
-
|
|
28
|
+
- **Tenant selection** — `airs tenant create|switch|list|read|delete` selects existing config files without copying secrets or changing read-only mounts
|
|
29
|
+
- **Profile migration** — `airs runtime profiles backup|restore` exports private JSON/YAML, previews cross-tenant restores, remaps topics and explicit DLP dependencies, and verifies restored policies ([guide](https://cdot65.github.io/prisma-airs-cli/runtime/profile-transfer/))
|
|
30
|
+
- **`airs config`** — manage the selected config file from the CLI (`list`, `get`, `set`, `unset`, `path`)
|
|
29
31
|
|
|
30
32
|
## Install
|
|
31
33
|
|
|
@@ -679,8 +679,11 @@ var SdkModelSecurityService = class {
|
|
|
679
679
|
}
|
|
680
680
|
async listScans(opts) {
|
|
681
681
|
const sdkOpts = {};
|
|
682
|
-
if (opts?.evalOutcome) sdkOpts.
|
|
683
|
-
if (opts?.sourceType) sdkOpts.
|
|
682
|
+
if (opts?.evalOutcome) sdkOpts.eval_outcomes = [opts.evalOutcome];
|
|
683
|
+
if (opts?.sourceType) sdkOpts.source_types = [opts.sourceType];
|
|
684
|
+
if (opts?.modelVersionUuid) sdkOpts.model_version_uuid = opts.modelVersionUuid;
|
|
685
|
+
if (opts?.startTime) sdkOpts.start_time = opts.startTime;
|
|
686
|
+
if (opts?.endTime) sdkOpts.end_time = opts.endTime;
|
|
684
687
|
if (opts?.scanOrigin) sdkOpts.scan_origin = opts.scanOrigin;
|
|
685
688
|
if (opts?.search) sdkOpts.search = opts.search;
|
|
686
689
|
if (opts?.skip !== void 0) sdkOpts.skip = opts.skip;
|
|
@@ -694,6 +697,9 @@ var SdkModelSecurityService = class {
|
|
|
694
697
|
}
|
|
695
698
|
async listAllScans(opts = {}) {
|
|
696
699
|
const rows = await this.client.scans.listAll({
|
|
700
|
+
model_version_uuid: opts.modelVersionUuid,
|
|
701
|
+
start_time: opts.startTime,
|
|
702
|
+
end_time: opts.endTime,
|
|
697
703
|
eval_outcomes: opts.evalOutcome === void 0 ? void 0 : [opts.evalOutcome],
|
|
698
704
|
source_types: opts.sourceType === void 0 ? void 0 : [opts.sourceType],
|
|
699
705
|
search_query: opts.search,
|
|
@@ -793,6 +799,8 @@ var SdkModelSecurityService = class {
|
|
|
793
799
|
const sdkOpts = {};
|
|
794
800
|
if (opts?.search) sdkOpts.search = opts.search;
|
|
795
801
|
if (opts?.searchQuery) sdkOpts.search_query = opts.searchQuery;
|
|
802
|
+
if (opts?.startTime) sdkOpts.start_time = opts.startTime;
|
|
803
|
+
if (opts?.endTime) sdkOpts.end_time = opts.endTime;
|
|
796
804
|
if (opts?.sortField) sdkOpts.sort_field = opts.sortField;
|
|
797
805
|
if (opts?.sortOrder) sdkOpts.sort_order = opts.sortOrder;
|
|
798
806
|
if (opts?.skip !== void 0) sdkOpts.skip = opts.skip;
|
|
@@ -806,6 +814,8 @@ var SdkModelSecurityService = class {
|
|
|
806
814
|
}
|
|
807
815
|
async listAllModels(opts = {}) {
|
|
808
816
|
const rows = await this.client.models.listAllModels({
|
|
817
|
+
start_time: opts.startTime,
|
|
818
|
+
end_time: opts.endTime,
|
|
809
819
|
search: opts.search,
|
|
810
820
|
search_query: opts.searchQuery,
|
|
811
821
|
sort_field: opts.sortField,
|
|
@@ -1444,7 +1454,7 @@ var SdkRedTeamService = class {
|
|
|
1444
1454
|
}));
|
|
1445
1455
|
}
|
|
1446
1456
|
async waitForCompletion(jobId, onProgress, intervalMs = 5e3) {
|
|
1447
|
-
const delay = (ms) => new Promise((
|
|
1457
|
+
const delay = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1448
1458
|
while (true) {
|
|
1449
1459
|
const job = await this.getScan(jobId);
|
|
1450
1460
|
onProgress?.(job);
|
|
@@ -1816,7 +1826,7 @@ var SdkRuntimeService = class {
|
|
|
1816
1826
|
const retryAfterMs = error.retryAfterMs;
|
|
1817
1827
|
const delayMs = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0 ? retryAfterMs : baseDelay * 2 ** (retryAttempt - 1);
|
|
1818
1828
|
retryOpts?.onRetry?.(retryAttempt, delayMs);
|
|
1819
|
-
await new Promise((
|
|
1829
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
1820
1830
|
}
|
|
1821
1831
|
}
|
|
1822
1832
|
return {
|
|
@@ -1861,7 +1871,7 @@ var SdkRuntimeService = class {
|
|
|
1861
1871
|
const retryAfterMs = err.retryAfterMs;
|
|
1862
1872
|
const delayMs = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0 ? retryAfterMs : baseDelay * 2 ** (retryLevel - 1);
|
|
1863
1873
|
retryOpts?.onRetry?.(retryLevel, delayMs);
|
|
1864
|
-
await new Promise((
|
|
1874
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
1865
1875
|
continue;
|
|
1866
1876
|
}
|
|
1867
1877
|
throw err;
|
|
@@ -1934,7 +1944,7 @@ var SdkRuntimeService = class {
|
|
|
1934
1944
|
const retryAfterMs = err.retryAfterMs;
|
|
1935
1945
|
const delayMs = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0 ? retryAfterMs : baseDelay * 2 ** (retryLevel - 1);
|
|
1936
1946
|
retryOpts?.onRetry?.(retryLevel, delayMs);
|
|
1937
|
-
await new Promise((
|
|
1947
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
1938
1948
|
continue;
|
|
1939
1949
|
}
|
|
1940
1950
|
throw err;
|
|
@@ -1968,7 +1978,7 @@ var SdkRuntimeService = class {
|
|
|
1968
1978
|
);
|
|
1969
1979
|
}
|
|
1970
1980
|
if (resolved.size < batch.entries.length) {
|
|
1971
|
-
await new Promise((
|
|
1981
|
+
await new Promise((resolve2) => setTimeout(resolve2, intervalMs));
|
|
1972
1982
|
}
|
|
1973
1983
|
}
|
|
1974
1984
|
return [...resolved.values()].sort((left, right) => left.index - right.index);
|
|
@@ -2016,7 +2026,7 @@ var SdkRuntimeService = class {
|
|
|
2016
2026
|
retryLevel++;
|
|
2017
2027
|
const delayMs = baseDelay * 2 ** (retryLevel - 1);
|
|
2018
2028
|
retryOpts?.onRetry?.(retryLevel, delayMs);
|
|
2019
|
-
await new Promise((
|
|
2029
|
+
await new Promise((resolve2) => setTimeout(resolve2, delayMs));
|
|
2020
2030
|
sweepCompleted = false;
|
|
2021
2031
|
break;
|
|
2022
2032
|
}
|
|
@@ -2025,7 +2035,7 @@ var SdkRuntimeService = class {
|
|
|
2025
2035
|
this.processQueryResults(results, completed, pending);
|
|
2026
2036
|
if (b + 5 < pendingIds.length) {
|
|
2027
2037
|
const batchDelay = retryLevel > 0 ? baseDelay : Math.min(baseDelay, 1e3);
|
|
2028
|
-
await new Promise((
|
|
2038
|
+
await new Promise((resolve2) => setTimeout(resolve2, batchDelay));
|
|
2029
2039
|
}
|
|
2030
2040
|
}
|
|
2031
2041
|
if (sweepCompleted && retryLevel > 0) {
|
|
@@ -2033,7 +2043,7 @@ var SdkRuntimeService = class {
|
|
|
2033
2043
|
}
|
|
2034
2044
|
if (pending.size > 0) {
|
|
2035
2045
|
const sweepDelay = retryLevel > 0 ? baseDelay * 2 ** retryLevel : intervalMs;
|
|
2036
|
-
await new Promise((
|
|
2046
|
+
await new Promise((resolve2) => setTimeout(resolve2, sweepDelay));
|
|
2037
2047
|
}
|
|
2038
2048
|
}
|
|
2039
2049
|
return scanIds.map((id) => completed.get(id));
|
|
@@ -2154,7 +2164,7 @@ var RateLimitedScanService = class {
|
|
|
2154
2164
|
const oldest = this.timestamps[0];
|
|
2155
2165
|
const waitMs = 1e3 - (now - oldest);
|
|
2156
2166
|
if (waitMs > 0) {
|
|
2157
|
-
await new Promise((
|
|
2167
|
+
await new Promise((resolve2) => setTimeout(resolve2, waitMs));
|
|
2158
2168
|
}
|
|
2159
2169
|
const after = Date.now();
|
|
2160
2170
|
this.timestamps = this.timestamps.filter((t) => after - t < 1e3);
|
|
@@ -2223,8 +2233,8 @@ function readBackupDir(dirPath, resourceType) {
|
|
|
2223
2233
|
|
|
2224
2234
|
// src/config/loader.ts
|
|
2225
2235
|
import { readFile } from "fs/promises";
|
|
2226
|
-
import { homedir } from "os";
|
|
2227
|
-
import { join } from "path";
|
|
2236
|
+
import { homedir as homedir2 } from "os";
|
|
2237
|
+
import { join as join2 } from "path";
|
|
2228
2238
|
|
|
2229
2239
|
// src/config/schema.ts
|
|
2230
2240
|
import { z } from "zod";
|
|
@@ -2251,6 +2261,9 @@ var ConfigSchema = z.object({
|
|
|
2251
2261
|
modelSecDataEndpoint: z.string().optional(),
|
|
2252
2262
|
modelSecMgmtEndpoint: z.string().optional(),
|
|
2253
2263
|
modelSecTokenEndpoint: z.string().optional(),
|
|
2264
|
+
agentGuardDataEndpoint: z.string().url().optional(),
|
|
2265
|
+
agentGuardMgmtEndpoint: z.string().url().optional(),
|
|
2266
|
+
agentGuardTokenEndpoint: z.string().url().optional(),
|
|
2254
2267
|
// AI Gateway (endpoints only; creds shared with mgmt*)
|
|
2255
2268
|
aiGwDataEndpoint: z.string().optional(),
|
|
2256
2269
|
aiGwAdminEndpoint: z.string().optional(),
|
|
@@ -2267,9 +2280,183 @@ var ConfigSchema = z.object({
|
|
|
2267
2280
|
dataDir: z.string().default("~/.prisma-airs/runs")
|
|
2268
2281
|
});
|
|
2269
2282
|
|
|
2283
|
+
// src/config/tenants.ts
|
|
2284
|
+
import { randomUUID } from "crypto";
|
|
2285
|
+
import { lstatSync, readFileSync, realpathSync } from "fs";
|
|
2286
|
+
import { mkdir, open, rename, unlink } from "fs/promises";
|
|
2287
|
+
import { homedir } from "os";
|
|
2288
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
2289
|
+
import { z as z2 } from "zod";
|
|
2290
|
+
var NameSchema = z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/).refine((v) => v !== "default");
|
|
2291
|
+
var EntrySchema = z2.object({
|
|
2292
|
+
name: NameSchema,
|
|
2293
|
+
configPath: z2.string().refine(isAbsolute),
|
|
2294
|
+
tsgId: z2.string().min(1)
|
|
2295
|
+
}).strict();
|
|
2296
|
+
var StoreSchema = z2.object({
|
|
2297
|
+
version: z2.literal(1),
|
|
2298
|
+
active: z2.string().nullable(),
|
|
2299
|
+
tenants: z2.array(EntrySchema).max(1e3)
|
|
2300
|
+
}).strict().superRefine((value, ctx) => {
|
|
2301
|
+
const names = new Set(value.tenants.map((entry) => entry.name));
|
|
2302
|
+
if (names.size !== value.tenants.length || value.active !== null && !names.has(value.active))
|
|
2303
|
+
ctx.addIssue({ code: z2.ZodIssueCode.custom, message: "Invalid tenant registry identities" });
|
|
2304
|
+
});
|
|
2305
|
+
function tenantStorePath() {
|
|
2306
|
+
const explicit = process.env.PRISMA_AIRS_TENANTS_PATH;
|
|
2307
|
+
if (explicit) return resolve(expandConfigPath(explicit));
|
|
2308
|
+
const state = process.env.XDG_STATE_HOME;
|
|
2309
|
+
if (state && !isAbsolute(state)) throw new Error("XDG_STATE_HOME must be an absolute path");
|
|
2310
|
+
return join(state || join(homedir(), ".local", "state"), "prisma-airs", "tenants.json");
|
|
2311
|
+
}
|
|
2312
|
+
function expandConfigPath(path2) {
|
|
2313
|
+
return path2 === "~" ? homedir() : path2.startsWith("~/") ? join(homedir(), path2.slice(2)) : path2;
|
|
2314
|
+
}
|
|
2315
|
+
function defaultTenantConfigPath() {
|
|
2316
|
+
return join(homedir(), ".prisma-airs", "config.json");
|
|
2317
|
+
}
|
|
2318
|
+
function missing(error) {
|
|
2319
|
+
return error?.code === "ENOENT";
|
|
2320
|
+
}
|
|
2321
|
+
function readTenantStore() {
|
|
2322
|
+
const path2 = tenantStorePath();
|
|
2323
|
+
try {
|
|
2324
|
+
const stat = lstatSync(path2);
|
|
2325
|
+
if (!stat.isFile() || stat.size > 1048576) throw new Error();
|
|
2326
|
+
return StoreSchema.parse(JSON.parse(readFileSync(path2, "utf8")));
|
|
2327
|
+
} catch (error) {
|
|
2328
|
+
if (missing(error)) return { version: 1, active: null, tenants: [] };
|
|
2329
|
+
throw new Error(
|
|
2330
|
+
`Cannot read tenant registry at ${path2}; restore a valid registry before proceeding`
|
|
2331
|
+
);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
function selectedTenant() {
|
|
2335
|
+
const store = readTenantStore();
|
|
2336
|
+
return store.tenants.find((entry) => entry.name === store.active);
|
|
2337
|
+
}
|
|
2338
|
+
function readTenantConfig(path2, expectedTsgId) {
|
|
2339
|
+
return ConfigSchema.parse(readTenantConfigFile(path2, expectedTsgId));
|
|
2340
|
+
}
|
|
2341
|
+
function readTenantConfigFile(path2, expectedTsgId) {
|
|
2342
|
+
let config;
|
|
2343
|
+
let parsed;
|
|
2344
|
+
try {
|
|
2345
|
+
const stat = lstatSync(realpathSync(path2));
|
|
2346
|
+
if (!stat.isFile() || stat.size > 1048576) throw new Error();
|
|
2347
|
+
parsed = JSON.parse(readFileSync(path2, "utf8"));
|
|
2348
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error();
|
|
2349
|
+
config = ConfigSchema.parse(parsed);
|
|
2350
|
+
} catch {
|
|
2351
|
+
throw new Error(`Cannot read a valid tenant config at ${path2}`);
|
|
2352
|
+
}
|
|
2353
|
+
if (expectedTsgId !== void 0 && config.mgmtTsgId !== expectedTsgId)
|
|
2354
|
+
throw new Error(
|
|
2355
|
+
"Tenant config TSG differs from its registered identity; register it under a new name"
|
|
2356
|
+
);
|
|
2357
|
+
return parsed;
|
|
2358
|
+
}
|
|
2359
|
+
function assertTenantEnvironment() {
|
|
2360
|
+
const conflicts = Object.keys(process.env).filter(
|
|
2361
|
+
(key) => /^PANW_(?:MGMT_|MODEL_SEC_|RED_TEAM_|AGENT_GUARD_|AI_GW_|DLP_|AI_SEC_API_)/.test(key) && Boolean(process.env[key])
|
|
2362
|
+
);
|
|
2363
|
+
if (conflicts.length)
|
|
2364
|
+
throw new Error(
|
|
2365
|
+
`Named tenant selection conflicts with environment overrides: ${conflicts.sort().join(", ")}. Unset them or switch to default.`
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
async function changeStore(change) {
|
|
2369
|
+
const path2 = tenantStorePath();
|
|
2370
|
+
await mkdir(dirname(path2), { recursive: true, mode: 448 });
|
|
2371
|
+
const lockPath = `${path2}.lock`;
|
|
2372
|
+
let lock;
|
|
2373
|
+
try {
|
|
2374
|
+
lock = await open(lockPath, "wx", 384);
|
|
2375
|
+
} catch {
|
|
2376
|
+
throw new Error(
|
|
2377
|
+
`Cannot lock tenant registry at ${lockPath}; another change may be in progress`
|
|
2378
|
+
);
|
|
2379
|
+
}
|
|
2380
|
+
const temp = join(dirname(path2), `.airs-tenants-${randomUUID()}.tmp`);
|
|
2381
|
+
let created = false;
|
|
2382
|
+
try {
|
|
2383
|
+
const store = readTenantStore();
|
|
2384
|
+
change(store);
|
|
2385
|
+
StoreSchema.parse(store);
|
|
2386
|
+
const file = await open(temp, "wx", 384);
|
|
2387
|
+
created = true;
|
|
2388
|
+
try {
|
|
2389
|
+
await file.writeFile(`${JSON.stringify(store, null, 2)}
|
|
2390
|
+
`);
|
|
2391
|
+
await file.sync();
|
|
2392
|
+
} finally {
|
|
2393
|
+
await file.close();
|
|
2394
|
+
}
|
|
2395
|
+
await rename(temp, path2);
|
|
2396
|
+
created = false;
|
|
2397
|
+
return store;
|
|
2398
|
+
} finally {
|
|
2399
|
+
if (created) await unlink(temp).catch(() => {
|
|
2400
|
+
});
|
|
2401
|
+
await lock.close();
|
|
2402
|
+
await unlink(lockPath);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
async function createTenant(name, configPath) {
|
|
2406
|
+
if (!NameSchema.safeParse(name).success)
|
|
2407
|
+
throw new Error(
|
|
2408
|
+
"Tenant name must be 1\u201364 letters, digits, hyphens or underscores, start with a letter/digit, and not be default"
|
|
2409
|
+
);
|
|
2410
|
+
let path2;
|
|
2411
|
+
try {
|
|
2412
|
+
path2 = realpathSync(resolve(expandConfigPath(configPath)));
|
|
2413
|
+
} catch {
|
|
2414
|
+
throw new Error("Tenant config file does not exist or cannot be resolved");
|
|
2415
|
+
}
|
|
2416
|
+
const config = readTenantConfig(path2);
|
|
2417
|
+
if (!config.mgmtTsgId?.trim() || !config.mgmtClientId?.trim() || !config.mgmtClientSecret?.trim())
|
|
2418
|
+
throw new Error("Tenant config requires mgmtTsgId, mgmtClientId and mgmtClientSecret");
|
|
2419
|
+
const entry = { name, configPath: path2, tsgId: config.mgmtTsgId };
|
|
2420
|
+
await changeStore((store) => {
|
|
2421
|
+
if (store.tenants.some((tenant) => tenant.name === name))
|
|
2422
|
+
throw new Error("Tenant name already exists");
|
|
2423
|
+
store.tenants.push(entry);
|
|
2424
|
+
store.tenants.sort((a, b) => a.name.localeCompare(b.name));
|
|
2425
|
+
});
|
|
2426
|
+
return entry;
|
|
2427
|
+
}
|
|
2428
|
+
async function switchTenant(name) {
|
|
2429
|
+
let selected;
|
|
2430
|
+
await changeStore((store) => {
|
|
2431
|
+
if (name === "default") {
|
|
2432
|
+
store.active = null;
|
|
2433
|
+
return;
|
|
2434
|
+
}
|
|
2435
|
+
if (process.env.PRISMA_AIRS_CONFIG_PATH)
|
|
2436
|
+
throw new Error(
|
|
2437
|
+
"Unset PRISMA_AIRS_CONFIG_PATH before switching tenants; it overrides named selection"
|
|
2438
|
+
);
|
|
2439
|
+
assertTenantEnvironment();
|
|
2440
|
+
selected = store.tenants.find((entry) => entry.name === name);
|
|
2441
|
+
if (!selected) throw new Error("Tenant not found");
|
|
2442
|
+
readTenantConfig(selected.configPath, selected.tsgId);
|
|
2443
|
+
store.active = name;
|
|
2444
|
+
});
|
|
2445
|
+
return selected;
|
|
2446
|
+
}
|
|
2447
|
+
async function deleteTenant(name) {
|
|
2448
|
+
await changeStore((store) => {
|
|
2449
|
+
if (name === "default") throw new Error("The default tenant cannot be deleted");
|
|
2450
|
+
if (store.active === name)
|
|
2451
|
+
throw new Error("Switch to another tenant or default before deleting the active tenant");
|
|
2452
|
+
if (!store.tenants.some((entry) => entry.name === name)) throw new Error("Tenant not found");
|
|
2453
|
+
store.tenants = store.tenants.filter((entry) => entry.name !== name);
|
|
2454
|
+
});
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2270
2457
|
// src/config/loader.ts
|
|
2271
2458
|
function expandHome(p) {
|
|
2272
|
-
return p.startsWith("~") ?
|
|
2459
|
+
return p.startsWith("~") ? join2(homedir2(), p.slice(1)) : p;
|
|
2273
2460
|
}
|
|
2274
2461
|
function fromEnv() {
|
|
2275
2462
|
const env = process.env;
|
|
@@ -2292,6 +2479,9 @@ function fromEnv() {
|
|
|
2292
2479
|
modelSecDataEndpoint: env.PANW_MODEL_SEC_DATA_ENDPOINT,
|
|
2293
2480
|
modelSecMgmtEndpoint: env.PANW_MODEL_SEC_MGMT_ENDPOINT,
|
|
2294
2481
|
modelSecTokenEndpoint: env.PANW_MODEL_SEC_TOKEN_ENDPOINT,
|
|
2482
|
+
agentGuardDataEndpoint: env.PANW_AGENT_GUARD_DATA_ENDPOINT,
|
|
2483
|
+
agentGuardMgmtEndpoint: env.PANW_AGENT_GUARD_MGMT_ENDPOINT,
|
|
2484
|
+
agentGuardTokenEndpoint: env.PANW_AGENT_GUARD_TOKEN_ENDPOINT,
|
|
2295
2485
|
aiGwDataEndpoint: env.PANW_AI_GW_DATA_ENDPOINT,
|
|
2296
2486
|
aiGwAdminEndpoint: env.PANW_AI_GW_ADMIN_ENDPOINT,
|
|
2297
2487
|
aiGwTokenEndpoint: env.PANW_AI_GW_TOKEN_ENDPOINT,
|
|
@@ -2316,14 +2506,23 @@ function stripUndefined(obj) {
|
|
|
2316
2506
|
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0 && v !== ""));
|
|
2317
2507
|
}
|
|
2318
2508
|
function resolveConfigFilePath(configFilePath) {
|
|
2319
|
-
|
|
2509
|
+
return resolveConfigSource(configFilePath).path;
|
|
2510
|
+
}
|
|
2511
|
+
function resolveConfigSource(configFilePath) {
|
|
2512
|
+
if (configFilePath) return { path: expandHome(configFilePath) };
|
|
2320
2513
|
const envPath = process.env.PRISMA_AIRS_CONFIG_PATH;
|
|
2321
|
-
if (envPath) return expandHome(envPath);
|
|
2322
|
-
|
|
2514
|
+
if (envPath) return { path: expandHome(envPath) };
|
|
2515
|
+
const tenant = selectedTenant();
|
|
2516
|
+
return { path: tenant?.configPath ?? join2(homedir2(), ".prisma-airs", "config.json"), tenant };
|
|
2517
|
+
}
|
|
2518
|
+
async function readConfigSource(configFilePath) {
|
|
2519
|
+
const { path: path2, tenant } = resolveConfigSource(configFilePath);
|
|
2520
|
+
if (!tenant) return fromFile(path2);
|
|
2521
|
+
assertTenantEnvironment();
|
|
2522
|
+
return readTenantConfigFile(path2, tenant.tsgId);
|
|
2323
2523
|
}
|
|
2324
2524
|
async function loadConfig(cliOverrides = {}, configFilePath) {
|
|
2325
|
-
const
|
|
2326
|
-
const fileConfig = await fromFile(filePath);
|
|
2525
|
+
const fileConfig = await readConfigSource(configFilePath);
|
|
2327
2526
|
const envConfig = fromEnv();
|
|
2328
2527
|
const merged = {
|
|
2329
2528
|
...stripUndefined(fileConfig),
|
|
@@ -2337,8 +2536,7 @@ async function loadConfig(cliOverrides = {}, configFilePath) {
|
|
|
2337
2536
|
};
|
|
2338
2537
|
}
|
|
2339
2538
|
async function inspectConfig(configFilePath) {
|
|
2340
|
-
const
|
|
2341
|
-
const fileConfig = stripUndefined(await fromFile(filePath));
|
|
2539
|
+
const fileConfig = stripUndefined(await readConfigSource(configFilePath));
|
|
2342
2540
|
const envConfig = stripUndefined(fromEnv());
|
|
2343
2541
|
const merged = { ...fileConfig, ...envConfig };
|
|
2344
2542
|
const config = ConfigSchema.parse(merged);
|
|
@@ -2911,12 +3109,12 @@ function renderEnvironmentReportHtml(r) {
|
|
|
2911
3109
|
}
|
|
2912
3110
|
|
|
2913
3111
|
// src/reports/io.ts
|
|
2914
|
-
import { randomUUID } from "crypto";
|
|
2915
|
-
import { link, open, unlink } from "fs/promises";
|
|
2916
|
-
import { dirname, join as
|
|
3112
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
3113
|
+
import { link, open as open2, unlink as unlink2 } from "fs/promises";
|
|
3114
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
2917
3115
|
async function writeReportFile(path2, content) {
|
|
2918
|
-
const temporary =
|
|
2919
|
-
const file = await
|
|
3116
|
+
const temporary = join3(dirname2(path2), `.airs-report-${randomUUID2()}.tmp`);
|
|
3117
|
+
const file = await open2(temporary, "wx", 384);
|
|
2920
3118
|
try {
|
|
2921
3119
|
await file.writeFile(content, "utf8");
|
|
2922
3120
|
await file.sync();
|
|
@@ -2924,7 +3122,7 @@ async function writeReportFile(path2, content) {
|
|
|
2924
3122
|
await link(temporary, path2);
|
|
2925
3123
|
} finally {
|
|
2926
3124
|
await file.close();
|
|
2927
|
-
await
|
|
3125
|
+
await unlink2(temporary);
|
|
2928
3126
|
}
|
|
2929
3127
|
}
|
|
2930
3128
|
|
|
@@ -3988,6 +4186,13 @@ export {
|
|
|
3988
4186
|
readBackupFile,
|
|
3989
4187
|
readBackupDir,
|
|
3990
4188
|
ConfigSchema,
|
|
4189
|
+
expandConfigPath,
|
|
4190
|
+
defaultTenantConfigPath,
|
|
4191
|
+
readTenantStore,
|
|
4192
|
+
readTenantConfig,
|
|
4193
|
+
createTenant,
|
|
4194
|
+
switchTenant,
|
|
4195
|
+
deleteTenant,
|
|
3991
4196
|
resolveConfigFilePath,
|
|
3992
4197
|
loadConfig,
|
|
3993
4198
|
inspectConfig,
|