//native
/**
* Execute task
* 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.
*/
export async function main(
auth: RT.AristaCloudvision,
task_id: string,
wait_timeout_seconds: number = 0
) {
const before = await getTask(auth, task_id)
if (before.workOrderUserDefinedStatus !== "Pending") {
throw new Error(
`Task ${task_id} is ${before.workOrderUserDefinedStatus}, not Pending: nothing to execute`
)
}
await cv(auth, "/cvpservice/task/executeTask.do", {
body: { data: [task_id] },
})
const deadline = Date.now() + (wait_timeout_seconds ?? 0) * 1000
let task = await getTask(auth, task_id)
while (
!["Completed", "Failed", "Cancelled"].includes(
task.workOrderUserDefinedStatus
) &&
Date.now() < deadline
) {
await new Promise((resolve) => setTimeout(resolve, 5000))
task = await getTask(auth, task_id)
}
const status = task.workOrderUserDefinedStatus
if (status === "Failed" || status === "Cancelled") {
throw new Error(
`Task ${task_id} ${status.toLowerCase()} on ${task.workOrderDetails?.netElementHostName ?? "its device"}: see the task logs in CloudVision${task.ccIdV2 ? ` (change control ${task.ccIdV2})` : ""}`
)
}
if (wait_timeout_seconds && status !== "Completed") {
throw new Error(
`Task ${task_id} still ${status} after ${wait_timeout_seconds} s`
)
}
return task
}
async function getTask(auth: RT.AristaCloudvision, task_id: string) {
const task = await cv(
auth,
`/cvpservice/task/getTaskById.do?taskId=${encodeURIComponent(task_id)}`,
{ notFound: `Task ${task_id} not found` }
)
if (!task || task.workOrderId == null)
throw new Error(`Task ${task_id} not found`)
return task
}
// Calls CloudVision and turns its failures into readable errors. Legacy
// endpoints report errors as HTTP 200 with an errorCode field.
async function cv(
auth: RT.AristaCloudvision,
path: string,
options: { body?: unknown; notFound?: string; optional?: boolean } = {}
): Promise<any> {
const url = new URL(path, auth.base_url)
// cvprac sends legacy endpoints to /cvpservice on CVaaS and /web on
// on-premises CVP. Its is_cvaas flag is the resource's deployment field;
// when that is unset, a host under arista.io is taken as CVaaS.
const cvaas = auth.deployment
? auth.deployment === "cvaas"
: /(^|\.)arista\.io\.?$/i.test(url.hostname)
if (!cvaas && url.pathname.startsWith("/cvpservice/")) {
url.pathname = url.pathname.replace(/^\/cvpservice\//, "/web/")
}
const response = await fetch(url, {
method: options.body === undefined ? "GET" : "POST",
headers: {
Authorization: `Bearer ${auth.token}`,
...(options.body === undefined
? {}
: { "Content-Type": "application/json" }),
Accept: "application/json",
},
body: options.body === undefined ? undefined : JSON.stringify(options.body),
})
const text = await response.text()
if (
response.status === 401 ||
response.status === 403 ||
(!response.ok && text.includes("User is unauthorized"))
) {
throw new Error(
`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}`
)
}
const data =
text.trim() === ""
? {}
: /^\s*[[{]/.test(text)
? JSON.parse(text)
: undefined
const error = findError(data)
if (response.ok && data !== undefined && !error) return data
const detail = error
? `${error.errorCode} ${error.errorMessage ?? (error.errors ?? []).join("; ")}`
: `HTTP ${response.status} ${text.slice(0, 500)}`
// Legacy lookups answer an unknown name or ID with an errorCode (cvprac
// treats any error from getTaskById and getImageBundleByName that way).
if (
response.status === 404 ||
(error && (options.optional || options.notFound))
) {
if (options.optional) return undefined
if (options.notFound) throw new Error(`${options.notFound} (${detail})`)
}
if (response.ok && !error) {
throw new Error(
`CloudVision returned a non-JSON response for ${url.pathname}: check that base_url is the CloudVision address. ${detail}`
)
}
throw new Error(`CloudVision ${url.pathname} failed: ${detail}`)
}
function findError(value: any): any {
if (!value || typeof value !== "object") return undefined
// Like cvprac, only a truthy errorCode counts, at any depth.
if (!Array.isArray(value) && value.errorCode) return value
for (const item of Object.values(value)) {
const error = findError(item)
if (error) return error
}
return undefined
}
Submitted by hugo989 5 hours ago