@cdot65/prisma-airs-cli 5.8.3 → 5.8.5
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 +162 -27
- 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();
|
|
@@ -9718,11 +9719,19 @@ async function backupDlpResources(api, tsgId, options = {}) {
|
|
|
9718
9719
|
if (!record.id) throw new Error("Inventory dictionary has no id");
|
|
9719
9720
|
if (record.type !== "predefined" && !Array.isArray(record.keywords))
|
|
9720
9721
|
throw new Error(`Dictionary keywords were not returned by the region: ${record.name}`);
|
|
9721
|
-
|
|
9722
|
+
const stored = record.type === "predefined" ? { id: record.id, name: record.name, type: record.type } : record;
|
|
9723
|
+
dictionaries.set(record.id, structuredClone(stored));
|
|
9722
9724
|
};
|
|
9723
9725
|
const addPattern = (record) => {
|
|
9724
9726
|
if (!record.id) throw new Error("Inventory data pattern has no id");
|
|
9725
|
-
|
|
9727
|
+
const stored = record.type === "predefined" ? {
|
|
9728
|
+
id: record.id,
|
|
9729
|
+
name: record.name,
|
|
9730
|
+
type: record.type,
|
|
9731
|
+
...record.version != null ? { version: record.version } : {},
|
|
9732
|
+
...record.detection_config?.technique ? { detection_config: { technique: record.detection_config.technique } } : {}
|
|
9733
|
+
} : record;
|
|
9734
|
+
patterns.set(record.id, structuredClone(stored));
|
|
9726
9735
|
};
|
|
9727
9736
|
if (kinds.includes("dictionaries")) {
|
|
9728
9737
|
for (const record of inventory2.dictionaries)
|
|
@@ -9884,14 +9893,15 @@ function normalizedName(value) {
|
|
|
9884
9893
|
}
|
|
9885
9894
|
function predefinedCandidates(source, records) {
|
|
9886
9895
|
const wanted = normalizedName(source.name ?? "");
|
|
9887
|
-
const
|
|
9888
|
-
|
|
9889
|
-
(record
|
|
9890
|
-
|
|
9896
|
+
const scored = /* @__PURE__ */ new Map();
|
|
9897
|
+
for (const record of records) {
|
|
9898
|
+
if (record.type !== "predefined" || record.name == null || RETIRED_PATTERN.has(record.status ?? "active") || record.detection_config?.technique !== source.detection_config?.technique)
|
|
9899
|
+
continue;
|
|
9891
9900
|
const name = normalizedName(record.name);
|
|
9892
|
-
const score = name === wanted ?
|
|
9893
|
-
|
|
9894
|
-
}
|
|
9901
|
+
const score = name === wanted ? 2 : name.includes(wanted) || wanted.includes(name) ? 1 : 0;
|
|
9902
|
+
if (score > 0) scored.set(record.name, Math.max(scored.get(record.name) ?? 0, score));
|
|
9903
|
+
}
|
|
9904
|
+
return [...scored.entries()].sort(([aName, aScore], [bName, bScore]) => bScore - aScore || aName.localeCompare(bName)).slice(0, 3).map(([name]) => name);
|
|
9895
9905
|
}
|
|
9896
9906
|
function matchByName(records, name, retired, label) {
|
|
9897
9907
|
const matches = records.filter((record) => record.name === name && !retired(record));
|
|
@@ -9918,6 +9928,8 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
9918
9928
|
if (unknownMapped.length)
|
|
9919
9929
|
throw new Error(`--pattern-map names absent from the backup: ${unknownMapped.join(", ")}`);
|
|
9920
9930
|
const crossTenant = backup.source.tsgId !== destinationTsgId;
|
|
9931
|
+
const unresolved = [];
|
|
9932
|
+
options.onProgress?.({ phase: "inventories" });
|
|
9921
9933
|
const destination = await inventories(api, maxPages3);
|
|
9922
9934
|
const sourceTenant = tenantIdentity([...backup.patterns, ...backup.profiles]);
|
|
9923
9935
|
const destinationTenant = tenantIdentity([...destination.patterns, ...destination.profiles]);
|
|
@@ -9936,8 +9948,18 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
9936
9948
|
if (!source.id || !source.name) throw new Error("Backup dictionary lacks identity");
|
|
9937
9949
|
if (source.type === "predefined") {
|
|
9938
9950
|
const match2 = matchByName(destination.dictionaries, source.name, () => false, "dictionary");
|
|
9939
|
-
if (!match2?.id || match2.type !== "predefined")
|
|
9951
|
+
if (!match2?.id || match2.type !== "predefined") {
|
|
9952
|
+
if (options.skipUnresolved) {
|
|
9953
|
+
unresolved.push({
|
|
9954
|
+
kind: "dictionary",
|
|
9955
|
+
sourceId: source.id,
|
|
9956
|
+
name: source.name,
|
|
9957
|
+
reason: "missing predefined destination dictionary"
|
|
9958
|
+
});
|
|
9959
|
+
continue;
|
|
9960
|
+
}
|
|
9940
9961
|
throw new Error(`Missing predefined destination dictionary: ${source.name}`);
|
|
9962
|
+
}
|
|
9941
9963
|
dictionaryPlans.push({
|
|
9942
9964
|
sourceId: source.id,
|
|
9943
9965
|
name: source.name,
|
|
@@ -9981,10 +10003,16 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
9981
10003
|
);
|
|
9982
10004
|
const match2 = byId ?? matchByName(destination.patterns, source.name, retired, "data pattern");
|
|
9983
10005
|
if (!match2?.id || !match2.name || match2.type !== "predefined") {
|
|
9984
|
-
|
|
9985
|
-
|
|
9986
|
-
|
|
9987
|
-
|
|
10006
|
+
const candidates = predefinedCandidates(source, destination.patterns);
|
|
10007
|
+
if (options.skipUnresolved)
|
|
10008
|
+
unresolved.push({
|
|
10009
|
+
kind: "pattern",
|
|
10010
|
+
sourceId: source.id,
|
|
10011
|
+
name: source.name,
|
|
10012
|
+
reason: "no identity or name match in the destination catalog",
|
|
10013
|
+
candidates
|
|
10014
|
+
});
|
|
10015
|
+
else missingPredefined.push({ name: source.name, candidates });
|
|
9988
10016
|
continue;
|
|
9989
10017
|
}
|
|
9990
10018
|
patternPlans.push({
|
|
@@ -10007,10 +10035,20 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
10007
10035
|
});
|
|
10008
10036
|
continue;
|
|
10009
10037
|
}
|
|
10010
|
-
if (!portablePattern(source))
|
|
10038
|
+
if (!portablePattern(source)) {
|
|
10039
|
+
if (options.skipUnresolved) {
|
|
10040
|
+
unresolved.push({
|
|
10041
|
+
kind: "pattern",
|
|
10042
|
+
sourceId: source.id,
|
|
10043
|
+
name: source.name,
|
|
10044
|
+
reason: "tenant-bound detection technique without --pattern-map"
|
|
10045
|
+
});
|
|
10046
|
+
continue;
|
|
10047
|
+
}
|
|
10011
10048
|
throw new Error(
|
|
10012
10049
|
`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>"`
|
|
10013
10050
|
);
|
|
10051
|
+
}
|
|
10014
10052
|
const name = `${prefix}${source.name}`;
|
|
10015
10053
|
assertName(name);
|
|
10016
10054
|
const body = patternRequest(source, name);
|
|
@@ -10069,6 +10107,7 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
10069
10107
|
dictionaries: dictionaryPlans,
|
|
10070
10108
|
patterns: patternPlans,
|
|
10071
10109
|
profiles: profilePlans,
|
|
10110
|
+
unresolved,
|
|
10072
10111
|
maxPages: maxPages3
|
|
10073
10112
|
};
|
|
10074
10113
|
const bindings = planBindings(plan);
|
|
@@ -10076,14 +10115,24 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
10076
10115
|
...dictionaryPlans.map((d) => d.sourceId),
|
|
10077
10116
|
...patternPlans.map((p) => p.sourceId)
|
|
10078
10117
|
]);
|
|
10118
|
+
const unresolvedById = new Map(unresolved.map((item) => [item.sourceId, item]));
|
|
10079
10119
|
for (const item of profilePlans) {
|
|
10080
10120
|
if (item.action === "skip") continue;
|
|
10121
|
+
const missing = /* @__PURE__ */ new Set();
|
|
10081
10122
|
for (const leaf of ruleLeaves(item.source)) {
|
|
10082
10123
|
const id = leaf.value.id;
|
|
10083
|
-
if (id != null && id !== "" && !planned.has(String(id)))
|
|
10084
|
-
|
|
10085
|
-
|
|
10086
|
-
|
|
10124
|
+
if (id != null && id !== "" && !planned.has(String(id))) missing.add(String(id));
|
|
10125
|
+
}
|
|
10126
|
+
if (missing.size) {
|
|
10127
|
+
const skippable = [...missing].filter((id) => unresolvedById.has(id));
|
|
10128
|
+
if (skippable.length === missing.size) {
|
|
10129
|
+
item.action = "skip";
|
|
10130
|
+
item.reason = `unresolved references: ${skippable.map((id) => unresolvedById.get(id)?.name).join(", ")}`;
|
|
10131
|
+
continue;
|
|
10132
|
+
}
|
|
10133
|
+
throw new Error(
|
|
10134
|
+
`Backup data profile references a resource absent from the backup: ${item.name}`
|
|
10135
|
+
);
|
|
10087
10136
|
}
|
|
10088
10137
|
const body = profileRequest(item, bindings, crossTenant, item.action === "verify");
|
|
10089
10138
|
if (item.action === "verify" && body && !compareEcho(body, item.existing).matches)
|
|
@@ -10153,7 +10202,7 @@ function assertUnchanged(current, prior, label) {
|
|
|
10153
10202
|
if (canonical(current ?? null) !== canonical(prior ?? null))
|
|
10154
10203
|
throw new Error(`Destination ${label} changed after planning`);
|
|
10155
10204
|
}
|
|
10156
|
-
async function restoreDlpResources(api, plan) {
|
|
10205
|
+
async function restoreDlpResources(api, plan, options = {}) {
|
|
10157
10206
|
const result = {
|
|
10158
10207
|
sourceTsgId: plan.sourceTsgId,
|
|
10159
10208
|
destinationTsgId: plan.destinationTsgId,
|
|
@@ -10161,10 +10210,14 @@ async function restoreDlpResources(api, plan) {
|
|
|
10161
10210
|
dictionaries: [],
|
|
10162
10211
|
patterns: [],
|
|
10163
10212
|
profiles: [],
|
|
10164
|
-
serverAdded: []
|
|
10213
|
+
serverAdded: [],
|
|
10214
|
+
unresolved: structuredClone(plan.unresolved ?? [])
|
|
10165
10215
|
};
|
|
10166
10216
|
const crossTenant = plan.sourceTsgId !== plan.destinationTsgId;
|
|
10217
|
+
const emit2 = options.onProgress;
|
|
10218
|
+
const emitItem = (stage, name, action, index, total) => emit2?.({ phase: "item", stage, name, action, index, total });
|
|
10167
10219
|
try {
|
|
10220
|
+
emit2?.({ phase: "recheck" });
|
|
10168
10221
|
const current = await inventories(api, plan.maxPages);
|
|
10169
10222
|
for (const item of plan.dictionaries) {
|
|
10170
10223
|
const match = matchByName(current.dictionaries, item.name, () => false, "dictionary");
|
|
@@ -10191,11 +10244,20 @@ async function restoreDlpResources(api, plan) {
|
|
|
10191
10244
|
throw new Error(`Destination data profile changed after planning: ${item.name}`);
|
|
10192
10245
|
}
|
|
10193
10246
|
const bindings = planBindings(plan);
|
|
10247
|
+
if (plan.dictionaries.length)
|
|
10248
|
+
emit2?.({ phase: "stage", stage: "dictionaries", total: plan.dictionaries.length });
|
|
10194
10249
|
for (const item of plan.dictionaries) {
|
|
10195
10250
|
if (item.action !== "create") {
|
|
10196
10251
|
const id = item.existing?.id;
|
|
10197
10252
|
if (!id) throw new Error(`Destination dictionary has no identity: ${item.name}`);
|
|
10198
10253
|
result.dictionaries.push({ name: item.name, action: DEPENDENCY_ACTIONS[item.action], id });
|
|
10254
|
+
emitItem(
|
|
10255
|
+
"dictionaries",
|
|
10256
|
+
item.name,
|
|
10257
|
+
DEPENDENCY_ACTIONS[item.action],
|
|
10258
|
+
result.dictionaries.length,
|
|
10259
|
+
plan.dictionaries.length
|
|
10260
|
+
);
|
|
10199
10261
|
continue;
|
|
10200
10262
|
}
|
|
10201
10263
|
if (!item.metadata || !item.keywords) throw new Error("Dictionary plan is incomplete");
|
|
@@ -10214,12 +10276,28 @@ async function restoreDlpResources(api, plan) {
|
|
|
10214
10276
|
result.serverAdded.push({ resource: `dictionary ${item.name}`, fields: echo.serverAdded });
|
|
10215
10277
|
bindings.set(item.sourceId, { id: created.id, name: item.name });
|
|
10216
10278
|
result.dictionaries.push({ name: item.name, action: "created", id: created.id });
|
|
10279
|
+
emitItem(
|
|
10280
|
+
"dictionaries",
|
|
10281
|
+
item.name,
|
|
10282
|
+
"created",
|
|
10283
|
+
result.dictionaries.length,
|
|
10284
|
+
plan.dictionaries.length
|
|
10285
|
+
);
|
|
10217
10286
|
}
|
|
10287
|
+
if (plan.patterns.length)
|
|
10288
|
+
emit2?.({ phase: "stage", stage: "patterns", total: plan.patterns.length });
|
|
10218
10289
|
for (const item of plan.patterns) {
|
|
10219
10290
|
if (item.action !== "create") {
|
|
10220
10291
|
const id = item.existing?.id;
|
|
10221
10292
|
if (!id) throw new Error(`Destination data pattern has no identity: ${item.name}`);
|
|
10222
10293
|
result.patterns.push({ name: item.name, action: DEPENDENCY_ACTIONS[item.action], id });
|
|
10294
|
+
emitItem(
|
|
10295
|
+
"patterns",
|
|
10296
|
+
item.name,
|
|
10297
|
+
DEPENDENCY_ACTIONS[item.action],
|
|
10298
|
+
result.patterns.length,
|
|
10299
|
+
plan.patterns.length
|
|
10300
|
+
);
|
|
10223
10301
|
continue;
|
|
10224
10302
|
}
|
|
10225
10303
|
if (!item.body) throw new Error("Data pattern plan is incomplete");
|
|
@@ -10240,14 +10318,19 @@ async function restoreDlpResources(api, plan) {
|
|
|
10240
10318
|
version: readBack.version ?? void 0
|
|
10241
10319
|
});
|
|
10242
10320
|
result.patterns.push({ name: item.name, action: "created", id: created.id });
|
|
10321
|
+
emitItem("patterns", item.name, "created", result.patterns.length, plan.patterns.length);
|
|
10243
10322
|
}
|
|
10323
|
+
if (plan.profiles.length)
|
|
10324
|
+
emit2?.({ phase: "stage", stage: "profiles", total: plan.profiles.length });
|
|
10244
10325
|
for (const item of plan.profiles) {
|
|
10245
10326
|
if (item.action === "skip") {
|
|
10246
10327
|
result.profiles.push({
|
|
10247
10328
|
name: item.name,
|
|
10248
10329
|
action: "skipped",
|
|
10249
|
-
id: item.existing?.id ?? void 0
|
|
10330
|
+
id: item.existing?.id ?? void 0,
|
|
10331
|
+
...item.reason ? { reason: item.reason } : {}
|
|
10250
10332
|
});
|
|
10333
|
+
emitItem("profiles", item.name, "skipped", result.profiles.length, plan.profiles.length);
|
|
10251
10334
|
continue;
|
|
10252
10335
|
}
|
|
10253
10336
|
const body = profileRequest(item, bindings, crossTenant, true);
|
|
@@ -10266,6 +10349,7 @@ async function restoreDlpResources(api, plan) {
|
|
|
10266
10349
|
action: "verified",
|
|
10267
10350
|
id: item.existing?.id ?? void 0
|
|
10268
10351
|
});
|
|
10352
|
+
emitItem("profiles", item.name, "verified", result.profiles.length, plan.profiles.length);
|
|
10269
10353
|
continue;
|
|
10270
10354
|
}
|
|
10271
10355
|
const created = await api.profiles.create(body);
|
|
@@ -10280,6 +10364,7 @@ async function restoreDlpResources(api, plan) {
|
|
|
10280
10364
|
fields: echo.serverAdded
|
|
10281
10365
|
});
|
|
10282
10366
|
result.profiles.push({ name: item.name, action: "created", id: created.id });
|
|
10367
|
+
emitItem("profiles", item.name, "created", result.profiles.length, plan.profiles.length);
|
|
10283
10368
|
}
|
|
10284
10369
|
result.complete = true;
|
|
10285
10370
|
} catch (error) {
|
|
@@ -10349,6 +10434,31 @@ function failure(error) {
|
|
|
10349
10434
|
}
|
|
10350
10435
|
process.exitCode = 1;
|
|
10351
10436
|
}
|
|
10437
|
+
var STAGE_LABELS = {
|
|
10438
|
+
dictionaries: "dictionaries",
|
|
10439
|
+
patterns: "data patterns",
|
|
10440
|
+
profiles: "data profiles"
|
|
10441
|
+
};
|
|
10442
|
+
var ITEM_LABELS = {
|
|
10443
|
+
dictionaries: "dictionary",
|
|
10444
|
+
patterns: "data pattern",
|
|
10445
|
+
profiles: "data profile"
|
|
10446
|
+
};
|
|
10447
|
+
function progressRenderer(format) {
|
|
10448
|
+
if (format !== "pretty") return void 0;
|
|
10449
|
+
return (event) => {
|
|
10450
|
+
if (event.phase === "inventories")
|
|
10451
|
+
ui.info("Reading inventories (dictionaries, data patterns, data profiles)\u2026");
|
|
10452
|
+
else if (event.phase === "recheck") ui.info("Rechecking destination state before writing\u2026");
|
|
10453
|
+
else if (event.phase === "stage")
|
|
10454
|
+
ui.info(`Restoring ${STAGE_LABELS[event.stage]} (${event.total})\u2026`);
|
|
10455
|
+
else
|
|
10456
|
+
ui.bullet(
|
|
10457
|
+
`${ITEM_LABELS[event.stage]} ${event.action}: ${event.name} (${event.index}/${event.total})`,
|
|
10458
|
+
event.action === "skipped" ? "skip" : "success"
|
|
10459
|
+
);
|
|
10460
|
+
};
|
|
10461
|
+
}
|
|
10352
10462
|
function renderRestore(value, columns, format) {
|
|
10353
10463
|
if (format !== "pretty" && format !== "table" && format !== "markdown")
|
|
10354
10464
|
return formatOutput([value], columns, format);
|
|
@@ -10380,6 +10490,11 @@ ${markdown ? "\n" : ""}Destination TSG: ${value.destinationTsgId}`
|
|
|
10380
10490
|
) || "(none)"
|
|
10381
10491
|
);
|
|
10382
10492
|
}
|
|
10493
|
+
if (rows("unresolved").length) {
|
|
10494
|
+
blocks.push(heading("Unresolved references \u2014 dependent profiles skipped"));
|
|
10495
|
+
for (const entry of rows("unresolved"))
|
|
10496
|
+
blocks.push(`${entry.kind} ${entry.name}: ${entry.reason}`);
|
|
10497
|
+
}
|
|
10383
10498
|
if (rows("serverAdded").length) {
|
|
10384
10499
|
blocks.push(heading("Verified server-added fields (source omitted these)"));
|
|
10385
10500
|
for (const item of rows("serverAdded"))
|
|
@@ -10432,7 +10547,8 @@ function register6(dlp) {
|
|
|
10432
10547
|
const { backup: data, skipped } = await backupDlpResources(api, tsgId, {
|
|
10433
10548
|
resources: opts.resources,
|
|
10434
10549
|
maxPages: opts.maxPages,
|
|
10435
|
-
skipUnsupported: Boolean(opts.skipUnsupported)
|
|
10550
|
+
skipUnsupported: Boolean(opts.skipUnsupported),
|
|
10551
|
+
onProgress: progressRenderer(format)
|
|
10436
10552
|
});
|
|
10437
10553
|
for (const item of skipped)
|
|
10438
10554
|
ui.warning(`Skipped unsupported data profile: ${item.profile} (${item.reason})`);
|
|
@@ -10476,9 +10592,12 @@ function register6(dlp) {
|
|
|
10476
10592
|
"error"
|
|
10477
10593
|
).option(
|
|
10478
10594
|
"--pattern-map <source=destination>",
|
|
10479
|
-
"Bind a tenant-bound source pattern to an existing destination pattern (repeatable)",
|
|
10595
|
+
"Bind a tenant-bound or predefined source pattern to an existing destination pattern (repeatable)",
|
|
10480
10596
|
(value, prior) => [...prior, value],
|
|
10481
10597
|
[]
|
|
10598
|
+
).option(
|
|
10599
|
+
"--skip-unresolved",
|
|
10600
|
+
"Skip references with no destination match, and every profile depending on them; whole profiles only, each reported"
|
|
10482
10601
|
).option(
|
|
10483
10602
|
"--max-pages <n>",
|
|
10484
10603
|
"Maximum pages per inventory; incomplete inventories fail",
|
|
@@ -10523,19 +10642,34 @@ function register6(dlp) {
|
|
|
10523
10642
|
throw new CliUsageError(
|
|
10524
10643
|
"--expect-tsg does not match the selected destination tenant; no API request was made"
|
|
10525
10644
|
);
|
|
10645
|
+
const onProgress = progressRenderer(format);
|
|
10526
10646
|
const plan = await planDlpResourcesRestore(api, data, tsgId, {
|
|
10527
10647
|
namePrefix: opts.namePrefix,
|
|
10528
10648
|
onConflict: opts.onConflict,
|
|
10529
10649
|
patternMap: mappings,
|
|
10530
|
-
maxPages: opts.maxPages
|
|
10650
|
+
maxPages: opts.maxPages,
|
|
10651
|
+
skipUnresolved: Boolean(opts.skipUnresolved),
|
|
10652
|
+
onProgress
|
|
10531
10653
|
});
|
|
10654
|
+
for (const entry of plan.unresolved)
|
|
10655
|
+
ui.warning(
|
|
10656
|
+
`Unresolved ${entry.kind === "dictionary" ? "dictionary" : "data pattern"}: ${entry.name} (${entry.reason})${entry.candidates?.length ? `; candidates: ${entry.candidates.map((name) => `"${name}"`).join(", ")}` : ""}`
|
|
10657
|
+
);
|
|
10658
|
+
for (const profile of plan.profiles)
|
|
10659
|
+
if (profile.reason)
|
|
10660
|
+
ui.warning(`Skipping data profile: ${profile.name} (${profile.reason})`);
|
|
10532
10661
|
const summary = {
|
|
10533
10662
|
sourceTsgId: plan.sourceTsgId,
|
|
10534
10663
|
destinationTsgId: plan.destinationTsgId,
|
|
10535
10664
|
dryRun: Boolean(opts.dryRun),
|
|
10536
10665
|
dictionaries: plan.dictionaries.map((d) => ({ name: d.name, action: d.action })),
|
|
10537
10666
|
patterns: plan.patterns.map((p) => ({ name: p.name, action: p.action })),
|
|
10538
|
-
profiles: plan.profiles.map((p) => ({
|
|
10667
|
+
profiles: plan.profiles.map((p) => ({
|
|
10668
|
+
name: p.name,
|
|
10669
|
+
action: p.action,
|
|
10670
|
+
...p.reason ? { reason: p.reason } : {}
|
|
10671
|
+
})),
|
|
10672
|
+
unresolved: plan.unresolved.map(({ kind, name, reason }) => ({ kind, name, reason }))
|
|
10539
10673
|
};
|
|
10540
10674
|
const columns = [
|
|
10541
10675
|
{ key: "sourceTsgId", label: "Source TSG" },
|
|
@@ -10543,6 +10677,7 @@ function register6(dlp) {
|
|
|
10543
10677
|
{ key: "dictionaries", label: "Dictionaries" },
|
|
10544
10678
|
{ key: "patterns", label: "Data patterns" },
|
|
10545
10679
|
{ key: "profiles", label: "Data profiles" },
|
|
10680
|
+
{ key: "unresolved", label: "Unresolved references" },
|
|
10546
10681
|
{ key: "serverAdded", label: "Verified server-added fields" },
|
|
10547
10682
|
{ key: "dryRun", label: "Dry run" },
|
|
10548
10683
|
{ key: "complete", label: "Complete" }
|
|
@@ -10558,7 +10693,7 @@ function register6(dlp) {
|
|
|
10558
10693
|
Boolean(opts.force),
|
|
10559
10694
|
{ action: `restore DLP resources into TSG ${tsgId}` }
|
|
10560
10695
|
);
|
|
10561
|
-
const result = await restoreDlpResources(api, plan);
|
|
10696
|
+
const result = await restoreDlpResources(api, plan, { onProgress });
|
|
10562
10697
|
console.log(renderRestore({ ...result }, columns, format));
|
|
10563
10698
|
if (!result.complete) {
|
|
10564
10699
|
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.5",
|
|
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",
|