1 | |
2 |
|
3 | |
4 | * Execute task |
5 | * Execute a pending provisioning task (legacy Network Provisioning API); a task that is not Pending is refused. With wait_timeout_seconds set, wait for it to finish and fail with the task's status if it ends Failed or Cancelled; otherwise return once CloudVision has accepted it. To group tasks under an approval, use Create change control instead. |
6 | */ |
7 | export async function main( |
8 | auth: RT.AristaCloudvision, |
9 | task_id: string, |
10 | wait_timeout_seconds: number = 0 |
11 | ) { |
12 | const before = await getTask(auth, task_id) |
13 | if (before.workOrderUserDefinedStatus !== "Pending") { |
14 | throw new Error( |
15 | `Task ${task_id} is ${before.workOrderUserDefinedStatus}, not Pending: nothing to execute` |
16 | ) |
17 | } |
18 | await cv(auth, "/cvpservice/task/executeTask.do", { |
19 | body: { data: [task_id] }, |
20 | }) |
21 | const deadline = Date.now() + (wait_timeout_seconds ?? 0) * 1000 |
22 | let task = await getTask(auth, task_id) |
23 | while ( |
24 | !["Completed", "Failed", "Cancelled"].includes( |
25 | task.workOrderUserDefinedStatus |
26 | ) && |
27 | Date.now() < deadline |
28 | ) { |
29 | await new Promise((resolve) => setTimeout(resolve, 5000)) |
30 | task = await getTask(auth, task_id) |
31 | } |
32 | const status = task.workOrderUserDefinedStatus |
33 | if (status === "Failed" || status === "Cancelled") { |
34 | throw new Error( |
35 | `Task ${task_id} ${status.toLowerCase()} on ${task.workOrderDetails?.netElementHostName ?? "its device"}: see the task logs in CloudVision${task.ccIdV2 ? ` (change control ${task.ccIdV2})` : ""}` |
36 | ) |
37 | } |
38 | if (wait_timeout_seconds && status !== "Completed") { |
39 | throw new Error( |
40 | `Task ${task_id} still ${status} after ${wait_timeout_seconds} s` |
41 | ) |
42 | } |
43 | return task |
44 | } |
45 |
|
46 | async function getTask(auth: RT.AristaCloudvision, task_id: string) { |
47 | const task = await cv( |
48 | auth, |
49 | `/cvpservice/task/getTaskById.do?taskId=${encodeURIComponent(task_id)}`, |
50 | { notFound: `Task ${task_id} not found` } |
51 | ) |
52 | if (!task || task.workOrderId == null) |
53 | throw new Error(`Task ${task_id} not found`) |
54 | return task |
55 | } |
56 |
|
57 | |
58 | |
59 | async function cv( |
60 | auth: RT.AristaCloudvision, |
61 | path: string, |
62 | options: { body?: unknown; notFound?: string; optional?: boolean } = {} |
63 | ): Promise<any> { |
64 | const url = new URL(path, auth.base_url) |
65 | |
66 | |
67 | |
68 | const cvaas = auth.deployment |
69 | ? auth.deployment === "cvaas" |
70 | : /(^|\.)arista\.io\.?$/i.test(url.hostname) |
71 | if (!cvaas && url.pathname.startsWith("/cvpservice/")) { |
72 | url.pathname = url.pathname.replace(/^\/cvpservice\//, "/web/") |
73 | } |
74 | const response = await fetch(url, { |
75 | method: options.body === undefined ? "GET" : "POST", |
76 | headers: { |
77 | Authorization: `Bearer ${auth.token}`, |
78 | ...(options.body === undefined |
79 | ? {} |
80 | : { "Content-Type": "application/json" }), |
81 | Accept: "application/json", |
82 | }, |
83 | body: options.body === undefined ? undefined : JSON.stringify(options.body), |
84 | }) |
85 | const text = await response.text() |
86 | if ( |
87 | response.status === 401 || |
88 | response.status === 403 || |
89 | (!response.ok && text.includes("User is unauthorized")) |
90 | ) { |
91 | throw new Error( |
92 | `CloudVision refused the token (HTTP ${response.status}): check that the service-account token is valid, not expired, and that its role allows this call. ${text}` |
93 | ) |
94 | } |
95 | const data = |
96 | text.trim() === "" |
97 | ? {} |
98 | : /^\s*[[{]/.test(text) |
99 | ? JSON.parse(text) |
100 | : undefined |
101 | const error = findError(data) |
102 | if (response.ok && data !== undefined && !error) return data |
103 | const detail = error |
104 | ? `${error.errorCode} ${error.errorMessage ?? (error.errors ?? []).join("; ")}` |
105 | : `HTTP ${response.status} ${text.slice(0, 500)}` |
106 | |
107 | |
108 | if ( |
109 | response.status === 404 || |
110 | (error && (options.optional || options.notFound)) |
111 | ) { |
112 | if (options.optional) return undefined |
113 | if (options.notFound) throw new Error(`${options.notFound} (${detail})`) |
114 | } |
115 | if (response.ok && !error) { |
116 | throw new Error( |
117 | `CloudVision returned a non-JSON response for ${url.pathname}: check that base_url is the CloudVision address. ${detail}` |
118 | ) |
119 | } |
120 | throw new Error(`CloudVision ${url.pathname} failed: ${detail}`) |
121 | } |
122 |
|
123 | function findError(value: any): any { |
124 | if (!value || typeof value !== "object") return undefined |
125 | |
126 | if (!Array.isArray(value) && value.errorCode) return value |
127 | for (const item of Object.values(value)) { |
128 | const error = findError(item) |
129 | if (error) return error |
130 | } |
131 | return undefined |
132 | } |
133 |
|