1 | |
2 |
|
3 | function wapiUrl(auth: RT.InfobloxNios, path: string) { |
4 | const host = auth.host |
5 | .trim() |
6 | .replace(/^https?:\/\//, "") |
7 | .replace(/\/+$/, "") |
8 | const version = auth.wapi_version.trim().replace(/^v/, "") |
9 | return new URL(`https://${host}/wapi/v${version}/${path}`) |
10 | } |
11 |
|
12 | function refUrl(auth: RT.InfobloxNios, ref: string, type: string) { |
13 | |
14 | const segments = ref.split("/") |
15 | if ( |
16 | segments[0] !== type || |
17 | segments.length < 2 || |
18 | segments.some((s) => s === "." || s === "..") |
19 | ) { |
20 | throw new Error(`Expected a ${type} reference, got: ${ref}`) |
21 | } |
22 | return wapiUrl( |
23 | auth, |
24 | segments.map((s) => encodeURIComponent(s).replace(/%3A/gi, ":")).join("/") |
25 | ) |
26 | } |
27 |
|
28 | function basicAuth(auth: RT.InfobloxNios) { |
29 | const bytes = new TextEncoder().encode(`${auth.username}:${auth.password}`) |
30 | return `Basic ${btoa(String.fromCharCode(...bytes))}` |
31 | } |
32 |
|
33 | async function wapiError(response: Response) { |
34 | const text = await response.text() |
35 | if (response.status === 401) { |
36 | return new Error( |
37 | "401 Unauthorized: wrong username or password, or the user's admin group has no API access" |
38 | ) |
39 | } |
40 | try { |
41 | |
42 | const { Error: error, code, text: detail } = JSON.parse(text) |
43 | if (error) { |
44 | const message = |
45 | detail && !error.includes(detail) ? `${error}: ${detail}` : error |
46 | return new Error( |
47 | `${response.status} ${message}${code ? ` (${code})` : ""}` |
48 | ) |
49 | } |
50 | } catch {} |
51 | return new Error(`${response.status} ${text}`) |
52 | } |
53 |
|
54 | |
55 | function toExtattrs(extattrs: { [key: string]: any }) { |
56 | return Object.fromEntries( |
57 | Object.entries(extattrs).map(([name, v]) => [ |
58 | name, |
59 | v !== null && typeof v === "object" && !Array.isArray(v) |
60 | ? v |
61 | : { value: v }, |
62 | ]) |
63 | ) |
64 | } |
65 |
|
66 | |
67 | * Update fixed address |
68 | * Change the MAC address, name or comment of a static DHCP reservation, identified by its _ref (from Search fixed addresses). extattrs ({"Site": "East"}) adds or overwrites those attributes and keeps the others. Empty inputs are left unchanged. |
69 | */ |
70 | export async function main( |
71 | auth: RT.InfobloxNios, |
72 | ref: string, |
73 | mac: string | undefined, |
74 | name: string | undefined, |
75 | comment: string | undefined, |
76 | extattrs: { [key: string]: any } | undefined |
77 | ) { |
78 | const body: { [key: string]: any } = {} |
79 | if (mac) body.mac = mac |
80 | if (name) body.name = name |
81 | if (comment) body.comment = comment |
82 | |
83 | if (extattrs && Object.keys(extattrs).length > 0) |
84 | body["extattrs+"] = toExtattrs(extattrs) |
85 | if (Object.keys(body).length === 0) { |
86 | throw new Error("Nothing to update: set at least one field") |
87 | } |
88 |
|
89 | const url = refUrl(auth, ref, "fixedaddress") |
90 | url.searchParams.append( |
91 | "_return_fields+", |
92 | "ipv4addr,mac,network,network_view,name,comment,extattrs" |
93 | ) |
94 | url.searchParams.append("_return_as_object", "1") |
95 |
|
96 | const response = await fetch(url, { |
97 | method: "PUT", |
98 | headers: { |
99 | Authorization: basicAuth(auth), |
100 | "Content-Type": "application/json", |
101 | Accept: "application/json", |
102 | }, |
103 | body: JSON.stringify(body), |
104 | }) |
105 |
|
106 | if (!response.ok) { |
107 | throw await wapiError(response) |
108 | } |
109 |
|
110 | return await response.json() |
111 | } |
112 |
|