@cdot65/prisma-airs-cli 5.8.2 → 5.8.4
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/dist/cli/index.js +173 -20
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -9710,6 +9710,7 @@ async function backupDlpResources(api, tsgId, options = {}) {
|
|
|
9710
9710
|
const kinds = options.resources ?? [...DLP_RESOURCE_KINDS];
|
|
9711
9711
|
if (!kinds.length || kinds.some((kind) => !DLP_RESOURCE_KINDS.includes(kind)))
|
|
9712
9712
|
throw new Error("resources must name dictionaries, patterns and/or profiles");
|
|
9713
|
+
options.onProgress?.({ phase: "inventories" });
|
|
9713
9714
|
const inventory2 = await inventories(api, maxPages3);
|
|
9714
9715
|
tenantIdentity([...inventory2.patterns, ...inventory2.profiles]);
|
|
9715
9716
|
const dictionaries = /* @__PURE__ */ new Map();
|
|
@@ -9879,6 +9880,20 @@ function patternRequest(source, name) {
|
|
|
9879
9880
|
function portablePattern(record) {
|
|
9880
9881
|
return PORTABLE_TECHNIQUES.has(record.detection_config?.technique ?? "") && record.detection_config?.technique !== void 0;
|
|
9881
9882
|
}
|
|
9883
|
+
function normalizedName(value) {
|
|
9884
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
9885
|
+
}
|
|
9886
|
+
function predefinedCandidates(source, records) {
|
|
9887
|
+
const wanted = normalizedName(source.name ?? "");
|
|
9888
|
+
const wantedTokens = new Set(wanted.split(" "));
|
|
9889
|
+
return records.filter(
|
|
9890
|
+
(record) => record.type === "predefined" && record.name != null && !RETIRED_PATTERN.has(record.status ?? "active") && record.detection_config?.technique === source.detection_config?.technique
|
|
9891
|
+
).map((record) => {
|
|
9892
|
+
const name = normalizedName(record.name);
|
|
9893
|
+
const score = name === wanted ? 3 : name.includes(wanted) || wanted.includes(name) ? 2 : name.split(" ").some((token) => wantedTokens.has(token)) ? 1 : 0;
|
|
9894
|
+
return { name: record.name, score };
|
|
9895
|
+
}).filter((item) => item.score > 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name)).slice(0, 3).map((item) => item.name);
|
|
9896
|
+
}
|
|
9882
9897
|
function matchByName(records, name, retired, label) {
|
|
9883
9898
|
const matches = records.filter((record) => record.name === name && !retired(record));
|
|
9884
9899
|
if (matches.length > 1) throw new Error(`Ambiguous destination ${label} name: ${name}`);
|
|
@@ -9904,6 +9919,8 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
9904
9919
|
if (unknownMapped.length)
|
|
9905
9920
|
throw new Error(`--pattern-map names absent from the backup: ${unknownMapped.join(", ")}`);
|
|
9906
9921
|
const crossTenant = backup.source.tsgId !== destinationTsgId;
|
|
9922
|
+
const unresolved = [];
|
|
9923
|
+
options.onProgress?.({ phase: "inventories" });
|
|
9907
9924
|
const destination = await inventories(api, maxPages3);
|
|
9908
9925
|
const sourceTenant = tenantIdentity([...backup.patterns, ...backup.profiles]);
|
|
9909
9926
|
const destinationTenant = tenantIdentity([...destination.patterns, ...destination.profiles]);
|
|
@@ -9922,8 +9939,18 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
9922
9939
|
if (!source.id || !source.name) throw new Error("Backup dictionary lacks identity");
|
|
9923
9940
|
if (source.type === "predefined") {
|
|
9924
9941
|
const match2 = matchByName(destination.dictionaries, source.name, () => false, "dictionary");
|
|
9925
|
-
if (!match2?.id || match2.type !== "predefined")
|
|
9942
|
+
if (!match2?.id || match2.type !== "predefined") {
|
|
9943
|
+
if (options.skipUnresolved) {
|
|
9944
|
+
unresolved.push({
|
|
9945
|
+
kind: "dictionary",
|
|
9946
|
+
sourceId: source.id,
|
|
9947
|
+
name: source.name,
|
|
9948
|
+
reason: "missing predefined destination dictionary"
|
|
9949
|
+
});
|
|
9950
|
+
continue;
|
|
9951
|
+
}
|
|
9926
9952
|
throw new Error(`Missing predefined destination dictionary: ${source.name}`);
|
|
9953
|
+
}
|
|
9927
9954
|
dictionaryPlans.push({
|
|
9928
9955
|
sourceId: source.id,
|
|
9929
9956
|
name: source.name,
|
|
@@ -9957,18 +9984,31 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
9957
9984
|
});
|
|
9958
9985
|
}
|
|
9959
9986
|
const patternPlans = [];
|
|
9987
|
+
const missingPredefined = [];
|
|
9960
9988
|
for (const source of backup.patterns) {
|
|
9961
9989
|
if (!source.id || !source.name) throw new Error("Backup data pattern lacks identity");
|
|
9962
9990
|
const retired = (record) => RETIRED_PATTERN.has(record.status ?? "active");
|
|
9963
9991
|
if (source.type === "predefined" && !Object.hasOwn(patternMap2, source.name)) {
|
|
9964
|
-
const
|
|
9965
|
-
|
|
9966
|
-
|
|
9967
|
-
|
|
9968
|
-
|
|
9992
|
+
const byId = destination.patterns.find(
|
|
9993
|
+
(record) => record.id === source.id && record.type === "predefined" && !retired(record) && record.detection_config?.technique === source.detection_config?.technique
|
|
9994
|
+
);
|
|
9995
|
+
const match2 = byId ?? matchByName(destination.patterns, source.name, retired, "data pattern");
|
|
9996
|
+
if (!match2?.id || !match2.name || match2.type !== "predefined") {
|
|
9997
|
+
const candidates = predefinedCandidates(source, destination.patterns);
|
|
9998
|
+
if (options.skipUnresolved)
|
|
9999
|
+
unresolved.push({
|
|
10000
|
+
kind: "pattern",
|
|
10001
|
+
sourceId: source.id,
|
|
10002
|
+
name: source.name,
|
|
10003
|
+
reason: "no identity or name match in the destination catalog",
|
|
10004
|
+
candidates
|
|
10005
|
+
});
|
|
10006
|
+
else missingPredefined.push({ name: source.name, candidates });
|
|
10007
|
+
continue;
|
|
10008
|
+
}
|
|
9969
10009
|
patternPlans.push({
|
|
9970
10010
|
sourceId: source.id,
|
|
9971
|
-
name:
|
|
10011
|
+
name: match2.name,
|
|
9972
10012
|
action: "resolve",
|
|
9973
10013
|
existing: structuredClone(match2)
|
|
9974
10014
|
});
|
|
@@ -9986,10 +10026,20 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
9986
10026
|
});
|
|
9987
10027
|
continue;
|
|
9988
10028
|
}
|
|
9989
|
-
if (!portablePattern(source))
|
|
10029
|
+
if (!portablePattern(source)) {
|
|
10030
|
+
if (options.skipUnresolved) {
|
|
10031
|
+
unresolved.push({
|
|
10032
|
+
kind: "pattern",
|
|
10033
|
+
sourceId: source.id,
|
|
10034
|
+
name: source.name,
|
|
10035
|
+
reason: "tenant-bound detection technique without --pattern-map"
|
|
10036
|
+
});
|
|
10037
|
+
continue;
|
|
10038
|
+
}
|
|
9990
10039
|
throw new Error(
|
|
9991
10040
|
`Data pattern uses a tenant-bound detection technique: ${source.name}; provision it in the destination and bind it with --pattern-map "${source.name}=<destination-name>"`
|
|
9992
10041
|
);
|
|
10042
|
+
}
|
|
9993
10043
|
const name = `${prefix}${source.name}`;
|
|
9994
10044
|
assertName(name);
|
|
9995
10045
|
const body = patternRequest(source, name);
|
|
@@ -10012,6 +10062,12 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
10012
10062
|
existing: structuredClone(match)
|
|
10013
10063
|
});
|
|
10014
10064
|
}
|
|
10065
|
+
if (missingPredefined.length)
|
|
10066
|
+
throw new Error(
|
|
10067
|
+
`Missing predefined destination data patterns: ${missingPredefined.map(
|
|
10068
|
+
(item) => `${item.name}${item.candidates.length ? ` (candidates: ${item.candidates.map((name) => `"${name}"`).join(", ")})` : " (no candidates)"}`
|
|
10069
|
+
).join("; ")}; bind each with --pattern-map "<source-name>=<destination-name>"`
|
|
10070
|
+
);
|
|
10015
10071
|
const profilePlans = [];
|
|
10016
10072
|
for (const source of backup.profiles) {
|
|
10017
10073
|
if (!source.name) throw new Error("Backup data profile lacks identity");
|
|
@@ -10042,6 +10098,7 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
10042
10098
|
dictionaries: dictionaryPlans,
|
|
10043
10099
|
patterns: patternPlans,
|
|
10044
10100
|
profiles: profilePlans,
|
|
10101
|
+
unresolved,
|
|
10045
10102
|
maxPages: maxPages3
|
|
10046
10103
|
};
|
|
10047
10104
|
const bindings = planBindings(plan);
|
|
@@ -10049,14 +10106,24 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
10049
10106
|
...dictionaryPlans.map((d) => d.sourceId),
|
|
10050
10107
|
...patternPlans.map((p) => p.sourceId)
|
|
10051
10108
|
]);
|
|
10109
|
+
const unresolvedById = new Map(unresolved.map((item) => [item.sourceId, item]));
|
|
10052
10110
|
for (const item of profilePlans) {
|
|
10053
10111
|
if (item.action === "skip") continue;
|
|
10112
|
+
const missing = /* @__PURE__ */ new Set();
|
|
10054
10113
|
for (const leaf of ruleLeaves(item.source)) {
|
|
10055
10114
|
const id = leaf.value.id;
|
|
10056
|
-
if (id != null && id !== "" && !planned.has(String(id)))
|
|
10057
|
-
|
|
10058
|
-
|
|
10059
|
-
|
|
10115
|
+
if (id != null && id !== "" && !planned.has(String(id))) missing.add(String(id));
|
|
10116
|
+
}
|
|
10117
|
+
if (missing.size) {
|
|
10118
|
+
const skippable = [...missing].filter((id) => unresolvedById.has(id));
|
|
10119
|
+
if (skippable.length === missing.size) {
|
|
10120
|
+
item.action = "skip";
|
|
10121
|
+
item.reason = `unresolved references: ${skippable.map((id) => unresolvedById.get(id)?.name).join(", ")}`;
|
|
10122
|
+
continue;
|
|
10123
|
+
}
|
|
10124
|
+
throw new Error(
|
|
10125
|
+
`Backup data profile references a resource absent from the backup: ${item.name}`
|
|
10126
|
+
);
|
|
10060
10127
|
}
|
|
10061
10128
|
const body = profileRequest(item, bindings, crossTenant, item.action === "verify");
|
|
10062
10129
|
if (item.action === "verify" && body && !compareEcho(body, item.existing).matches)
|
|
@@ -10126,7 +10193,7 @@ function assertUnchanged(current, prior, label) {
|
|
|
10126
10193
|
if (canonical(current ?? null) !== canonical(prior ?? null))
|
|
10127
10194
|
throw new Error(`Destination ${label} changed after planning`);
|
|
10128
10195
|
}
|
|
10129
|
-
async function restoreDlpResources(api, plan) {
|
|
10196
|
+
async function restoreDlpResources(api, plan, options = {}) {
|
|
10130
10197
|
const result = {
|
|
10131
10198
|
sourceTsgId: plan.sourceTsgId,
|
|
10132
10199
|
destinationTsgId: plan.destinationTsgId,
|
|
@@ -10134,10 +10201,14 @@ async function restoreDlpResources(api, plan) {
|
|
|
10134
10201
|
dictionaries: [],
|
|
10135
10202
|
patterns: [],
|
|
10136
10203
|
profiles: [],
|
|
10137
|
-
serverAdded: []
|
|
10204
|
+
serverAdded: [],
|
|
10205
|
+
unresolved: structuredClone(plan.unresolved ?? [])
|
|
10138
10206
|
};
|
|
10139
10207
|
const crossTenant = plan.sourceTsgId !== plan.destinationTsgId;
|
|
10208
|
+
const emit2 = options.onProgress;
|
|
10209
|
+
const emitItem = (stage, name, action, index, total) => emit2?.({ phase: "item", stage, name, action, index, total });
|
|
10140
10210
|
try {
|
|
10211
|
+
emit2?.({ phase: "recheck" });
|
|
10141
10212
|
const current = await inventories(api, plan.maxPages);
|
|
10142
10213
|
for (const item of plan.dictionaries) {
|
|
10143
10214
|
const match = matchByName(current.dictionaries, item.name, () => false, "dictionary");
|
|
@@ -10164,11 +10235,20 @@ async function restoreDlpResources(api, plan) {
|
|
|
10164
10235
|
throw new Error(`Destination data profile changed after planning: ${item.name}`);
|
|
10165
10236
|
}
|
|
10166
10237
|
const bindings = planBindings(plan);
|
|
10238
|
+
if (plan.dictionaries.length)
|
|
10239
|
+
emit2?.({ phase: "stage", stage: "dictionaries", total: plan.dictionaries.length });
|
|
10167
10240
|
for (const item of plan.dictionaries) {
|
|
10168
10241
|
if (item.action !== "create") {
|
|
10169
10242
|
const id = item.existing?.id;
|
|
10170
10243
|
if (!id) throw new Error(`Destination dictionary has no identity: ${item.name}`);
|
|
10171
10244
|
result.dictionaries.push({ name: item.name, action: DEPENDENCY_ACTIONS[item.action], id });
|
|
10245
|
+
emitItem(
|
|
10246
|
+
"dictionaries",
|
|
10247
|
+
item.name,
|
|
10248
|
+
DEPENDENCY_ACTIONS[item.action],
|
|
10249
|
+
result.dictionaries.length,
|
|
10250
|
+
plan.dictionaries.length
|
|
10251
|
+
);
|
|
10172
10252
|
continue;
|
|
10173
10253
|
}
|
|
10174
10254
|
if (!item.metadata || !item.keywords) throw new Error("Dictionary plan is incomplete");
|
|
@@ -10187,12 +10267,28 @@ async function restoreDlpResources(api, plan) {
|
|
|
10187
10267
|
result.serverAdded.push({ resource: `dictionary ${item.name}`, fields: echo.serverAdded });
|
|
10188
10268
|
bindings.set(item.sourceId, { id: created.id, name: item.name });
|
|
10189
10269
|
result.dictionaries.push({ name: item.name, action: "created", id: created.id });
|
|
10270
|
+
emitItem(
|
|
10271
|
+
"dictionaries",
|
|
10272
|
+
item.name,
|
|
10273
|
+
"created",
|
|
10274
|
+
result.dictionaries.length,
|
|
10275
|
+
plan.dictionaries.length
|
|
10276
|
+
);
|
|
10190
10277
|
}
|
|
10278
|
+
if (plan.patterns.length)
|
|
10279
|
+
emit2?.({ phase: "stage", stage: "patterns", total: plan.patterns.length });
|
|
10191
10280
|
for (const item of plan.patterns) {
|
|
10192
10281
|
if (item.action !== "create") {
|
|
10193
10282
|
const id = item.existing?.id;
|
|
10194
10283
|
if (!id) throw new Error(`Destination data pattern has no identity: ${item.name}`);
|
|
10195
10284
|
result.patterns.push({ name: item.name, action: DEPENDENCY_ACTIONS[item.action], id });
|
|
10285
|
+
emitItem(
|
|
10286
|
+
"patterns",
|
|
10287
|
+
item.name,
|
|
10288
|
+
DEPENDENCY_ACTIONS[item.action],
|
|
10289
|
+
result.patterns.length,
|
|
10290
|
+
plan.patterns.length
|
|
10291
|
+
);
|
|
10196
10292
|
continue;
|
|
10197
10293
|
}
|
|
10198
10294
|
if (!item.body) throw new Error("Data pattern plan is incomplete");
|
|
@@ -10213,14 +10309,19 @@ async function restoreDlpResources(api, plan) {
|
|
|
10213
10309
|
version: readBack.version ?? void 0
|
|
10214
10310
|
});
|
|
10215
10311
|
result.patterns.push({ name: item.name, action: "created", id: created.id });
|
|
10312
|
+
emitItem("patterns", item.name, "created", result.patterns.length, plan.patterns.length);
|
|
10216
10313
|
}
|
|
10314
|
+
if (plan.profiles.length)
|
|
10315
|
+
emit2?.({ phase: "stage", stage: "profiles", total: plan.profiles.length });
|
|
10217
10316
|
for (const item of plan.profiles) {
|
|
10218
10317
|
if (item.action === "skip") {
|
|
10219
10318
|
result.profiles.push({
|
|
10220
10319
|
name: item.name,
|
|
10221
10320
|
action: "skipped",
|
|
10222
|
-
id: item.existing?.id ?? void 0
|
|
10321
|
+
id: item.existing?.id ?? void 0,
|
|
10322
|
+
...item.reason ? { reason: item.reason } : {}
|
|
10223
10323
|
});
|
|
10324
|
+
emitItem("profiles", item.name, "skipped", result.profiles.length, plan.profiles.length);
|
|
10224
10325
|
continue;
|
|
10225
10326
|
}
|
|
10226
10327
|
const body = profileRequest(item, bindings, crossTenant, true);
|
|
@@ -10239,6 +10340,7 @@ async function restoreDlpResources(api, plan) {
|
|
|
10239
10340
|
action: "verified",
|
|
10240
10341
|
id: item.existing?.id ?? void 0
|
|
10241
10342
|
});
|
|
10343
|
+
emitItem("profiles", item.name, "verified", result.profiles.length, plan.profiles.length);
|
|
10242
10344
|
continue;
|
|
10243
10345
|
}
|
|
10244
10346
|
const created = await api.profiles.create(body);
|
|
@@ -10253,6 +10355,7 @@ async function restoreDlpResources(api, plan) {
|
|
|
10253
10355
|
fields: echo.serverAdded
|
|
10254
10356
|
});
|
|
10255
10357
|
result.profiles.push({ name: item.name, action: "created", id: created.id });
|
|
10358
|
+
emitItem("profiles", item.name, "created", result.profiles.length, plan.profiles.length);
|
|
10256
10359
|
}
|
|
10257
10360
|
result.complete = true;
|
|
10258
10361
|
} catch (error) {
|
|
@@ -10322,6 +10425,31 @@ function failure(error) {
|
|
|
10322
10425
|
}
|
|
10323
10426
|
process.exitCode = 1;
|
|
10324
10427
|
}
|
|
10428
|
+
var STAGE_LABELS = {
|
|
10429
|
+
dictionaries: "dictionaries",
|
|
10430
|
+
patterns: "data patterns",
|
|
10431
|
+
profiles: "data profiles"
|
|
10432
|
+
};
|
|
10433
|
+
var ITEM_LABELS = {
|
|
10434
|
+
dictionaries: "dictionary",
|
|
10435
|
+
patterns: "data pattern",
|
|
10436
|
+
profiles: "data profile"
|
|
10437
|
+
};
|
|
10438
|
+
function progressRenderer(format) {
|
|
10439
|
+
if (format !== "pretty") return void 0;
|
|
10440
|
+
return (event) => {
|
|
10441
|
+
if (event.phase === "inventories")
|
|
10442
|
+
ui.info("Reading inventories (dictionaries, data patterns, data profiles)\u2026");
|
|
10443
|
+
else if (event.phase === "recheck") ui.info("Rechecking destination state before writing\u2026");
|
|
10444
|
+
else if (event.phase === "stage")
|
|
10445
|
+
ui.info(`Restoring ${STAGE_LABELS[event.stage]} (${event.total})\u2026`);
|
|
10446
|
+
else
|
|
10447
|
+
ui.bullet(
|
|
10448
|
+
`${ITEM_LABELS[event.stage]} ${event.action}: ${event.name} (${event.index}/${event.total})`,
|
|
10449
|
+
event.action === "skipped" ? "skip" : "success"
|
|
10450
|
+
);
|
|
10451
|
+
};
|
|
10452
|
+
}
|
|
10325
10453
|
function renderRestore(value, columns, format) {
|
|
10326
10454
|
if (format !== "pretty" && format !== "table" && format !== "markdown")
|
|
10327
10455
|
return formatOutput([value], columns, format);
|
|
@@ -10353,6 +10481,11 @@ ${markdown ? "\n" : ""}Destination TSG: ${value.destinationTsgId}`
|
|
|
10353
10481
|
) || "(none)"
|
|
10354
10482
|
);
|
|
10355
10483
|
}
|
|
10484
|
+
if (rows("unresolved").length) {
|
|
10485
|
+
blocks.push(heading("Unresolved references \u2014 dependent profiles skipped"));
|
|
10486
|
+
for (const entry of rows("unresolved"))
|
|
10487
|
+
blocks.push(`${entry.kind} ${entry.name}: ${entry.reason}`);
|
|
10488
|
+
}
|
|
10356
10489
|
if (rows("serverAdded").length) {
|
|
10357
10490
|
blocks.push(heading("Verified server-added fields (source omitted these)"));
|
|
10358
10491
|
for (const item of rows("serverAdded"))
|
|
@@ -10405,7 +10538,8 @@ function register6(dlp) {
|
|
|
10405
10538
|
const { backup: data, skipped } = await backupDlpResources(api, tsgId, {
|
|
10406
10539
|
resources: opts.resources,
|
|
10407
10540
|
maxPages: opts.maxPages,
|
|
10408
|
-
skipUnsupported: Boolean(opts.skipUnsupported)
|
|
10541
|
+
skipUnsupported: Boolean(opts.skipUnsupported),
|
|
10542
|
+
onProgress: progressRenderer(format)
|
|
10409
10543
|
});
|
|
10410
10544
|
for (const item of skipped)
|
|
10411
10545
|
ui.warning(`Skipped unsupported data profile: ${item.profile} (${item.reason})`);
|
|
@@ -10449,9 +10583,12 @@ function register6(dlp) {
|
|
|
10449
10583
|
"error"
|
|
10450
10584
|
).option(
|
|
10451
10585
|
"--pattern-map <source=destination>",
|
|
10452
|
-
"Bind a tenant-bound source pattern to an existing destination pattern (repeatable)",
|
|
10586
|
+
"Bind a tenant-bound or predefined source pattern to an existing destination pattern (repeatable)",
|
|
10453
10587
|
(value, prior) => [...prior, value],
|
|
10454
10588
|
[]
|
|
10589
|
+
).option(
|
|
10590
|
+
"--skip-unresolved",
|
|
10591
|
+
"Skip references with no destination match, and every profile depending on them; whole profiles only, each reported"
|
|
10455
10592
|
).option(
|
|
10456
10593
|
"--max-pages <n>",
|
|
10457
10594
|
"Maximum pages per inventory; incomplete inventories fail",
|
|
@@ -10496,19 +10633,34 @@ function register6(dlp) {
|
|
|
10496
10633
|
throw new CliUsageError(
|
|
10497
10634
|
"--expect-tsg does not match the selected destination tenant; no API request was made"
|
|
10498
10635
|
);
|
|
10636
|
+
const onProgress = progressRenderer(format);
|
|
10499
10637
|
const plan = await planDlpResourcesRestore(api, data, tsgId, {
|
|
10500
10638
|
namePrefix: opts.namePrefix,
|
|
10501
10639
|
onConflict: opts.onConflict,
|
|
10502
10640
|
patternMap: mappings,
|
|
10503
|
-
maxPages: opts.maxPages
|
|
10641
|
+
maxPages: opts.maxPages,
|
|
10642
|
+
skipUnresolved: Boolean(opts.skipUnresolved),
|
|
10643
|
+
onProgress
|
|
10504
10644
|
});
|
|
10645
|
+
for (const entry of plan.unresolved)
|
|
10646
|
+
ui.warning(
|
|
10647
|
+
`Unresolved ${entry.kind === "dictionary" ? "dictionary" : "data pattern"}: ${entry.name} (${entry.reason})${entry.candidates?.length ? `; candidates: ${entry.candidates.map((name) => `"${name}"`).join(", ")}` : ""}`
|
|
10648
|
+
);
|
|
10649
|
+
for (const profile of plan.profiles)
|
|
10650
|
+
if (profile.reason)
|
|
10651
|
+
ui.warning(`Skipping data profile: ${profile.name} (${profile.reason})`);
|
|
10505
10652
|
const summary = {
|
|
10506
10653
|
sourceTsgId: plan.sourceTsgId,
|
|
10507
10654
|
destinationTsgId: plan.destinationTsgId,
|
|
10508
10655
|
dryRun: Boolean(opts.dryRun),
|
|
10509
10656
|
dictionaries: plan.dictionaries.map((d) => ({ name: d.name, action: d.action })),
|
|
10510
10657
|
patterns: plan.patterns.map((p) => ({ name: p.name, action: p.action })),
|
|
10511
|
-
profiles: plan.profiles.map((p) => ({
|
|
10658
|
+
profiles: plan.profiles.map((p) => ({
|
|
10659
|
+
name: p.name,
|
|
10660
|
+
action: p.action,
|
|
10661
|
+
...p.reason ? { reason: p.reason } : {}
|
|
10662
|
+
})),
|
|
10663
|
+
unresolved: plan.unresolved.map(({ kind, name, reason }) => ({ kind, name, reason }))
|
|
10512
10664
|
};
|
|
10513
10665
|
const columns = [
|
|
10514
10666
|
{ key: "sourceTsgId", label: "Source TSG" },
|
|
@@ -10516,6 +10668,7 @@ function register6(dlp) {
|
|
|
10516
10668
|
{ key: "dictionaries", label: "Dictionaries" },
|
|
10517
10669
|
{ key: "patterns", label: "Data patterns" },
|
|
10518
10670
|
{ key: "profiles", label: "Data profiles" },
|
|
10671
|
+
{ key: "unresolved", label: "Unresolved references" },
|
|
10519
10672
|
{ key: "serverAdded", label: "Verified server-added fields" },
|
|
10520
10673
|
{ key: "dryRun", label: "Dry run" },
|
|
10521
10674
|
{ key: "complete", label: "Complete" }
|
|
@@ -10531,7 +10684,7 @@ function register6(dlp) {
|
|
|
10531
10684
|
Boolean(opts.force),
|
|
10532
10685
|
{ action: `restore DLP resources into TSG ${tsgId}` }
|
|
10533
10686
|
);
|
|
10534
|
-
const result = await restoreDlpResources(api, plan);
|
|
10687
|
+
const result = await restoreDlpResources(api, plan, { onProgress });
|
|
10535
10688
|
console.log(renderRestore({ ...result }, columns, format));
|
|
10536
10689
|
if (!result.complete) {
|
|
10537
10690
|
ui.error(result.error ?? "Restore incomplete");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cdot65/prisma-airs-cli",
|
|
3
3
|
"packageManager": "pnpm@10.6.5",
|
|
4
|
-
"version": "5.8.
|
|
4
|
+
"version": "5.8.4",
|
|
5
5
|
"description": "CLI and library for Palo Alto Prisma AIRS — guardrail refinement, AI red teaming, model security scanning, profile audits",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|