1 | import { XMLParser } from "fast-xml-parser" |
2 |
|
3 | |
4 | * Commit |
5 | * Commit the candidate configuration on a firewall, or on Panorama itself (PAN-OS XML API, type=commit). Set admins for a partial commit of only those administrators' changes. Returns the commit job id to follow with Get job status, or a null job id with the device message when there is nothing to commit. On Panorama this does not push to firewalls: use Push to device group. |
6 | */ |
7 | export async function main( |
8 | auth: RT.PaloAltoPanos, |
9 | description: string | undefined, |
10 | admins: string[] | undefined |
11 | ) { |
12 | let cmd = "<commit>" |
13 | if (description) cmd += `<description>${xmlEscape(description)}</description>` |
14 | if (admins?.length) { |
15 | cmd += `<partial><admin>${admins.map((a) => `<member>${xmlEscape(a)}</member>`).join("")}</admin></partial>` |
16 | } |
17 | cmd += "</commit>" |
18 |
|
19 | const res = await xmlApi(auth, { type: "commit", cmd }) |
20 | return { |
21 | job_id: res.result?.job != null ? String(res.result.job) : null, |
22 | message: |
23 | res.result?.msg?.line ?? |
24 | res.result?.msg ?? |
25 | res.msg?.line ?? |
26 | res.msg ?? |
27 | null, |
28 | } |
29 | } |
30 |
|
31 | function xmlEscape(s: string) { |
32 | return s |
33 | .replace(/&/g, "&") |
34 | .replace(/</g, "<") |
35 | .replace(/>/g, ">") |
36 | .replace(/"/g, """) |
37 | .replace(/'/g, "'") |
38 | } |
39 |
|
40 | async function xmlApi(auth: RT.PaloAltoPanos, params: { [key: string]: string }) { |
41 | const url = new URL("/api/", auth.base_url) |
42 | for (const [key, value] of Object.entries(params)) { |
43 | url.searchParams.set(key, value) |
44 | } |
45 | const response = await fetch(url, { |
46 | method: "POST", |
47 | headers: { "X-PAN-KEY": auth.api_key }, |
48 | }) |
49 | const text = await response.text() |
50 | if (!response.ok) { |
51 | throw new Error(`${response.status} ${text}`) |
52 | } |
53 | const parsed = new XMLParser({ |
54 | ignoreAttributes: false, |
55 | attributeNamePrefix: "@", |
56 | parseTagValue: false, |
57 | }).parse(text) |
58 | if (parsed.response?.["@status"] !== "success") { |
59 | throw new Error(`${response.status} ${text}`) |
60 | } |
61 | return parsed.response |
62 | } |
63 |
|