0

Get change control

by
Published today

Get a change control's stages, approval and execution status (resource API changecontrol.v1). status is CHANGE_CONTROL_STATUS_NOT_STARTED, _SCHEDULED, _RUNNING or _COMPLETED; a completed change control with an error failed.

Script arista_cloudvision Verified

The script

Submitted by hugo989 Typescript (fetch-only)
Verified 4 hours ago
1
//native
2

3
/**
4
 * Get change control
5
 * Get a change control's stages, approval and execution status (resource API changecontrol.v1). status is CHANGE_CONTROL_STATUS_NOT_STARTED, _SCHEDULED, _RUNNING or _COMPLETED; a completed change control with an error failed.
6
 */
7
export async function main(
8
  auth: RT.AristaCloudvision,
9
  change_control_id: string
10
) {
11
  const result = await cv(
12
    auth,
13
    `/api/resources/changecontrol/v1/ChangeControl?key.id=${encodeURIComponent(change_control_id)}`,
14
    { notFound: `Change control ${change_control_id} not found` }
15
  )
16
  return result.value
17
}
18

19
// Calls CloudVision and turns its failures into readable errors.
20
async function cv(
21
  auth: RT.AristaCloudvision,
22
  path: string,
23
  options: { body?: unknown; notFound?: string; optional?: boolean } = {}
24
): Promise<any> {
25
  const url = new URL(path, auth.base_url)
26
  const response = await fetch(url, {
27
    method: options.body === undefined ? "GET" : "POST",
28
    headers: {
29
      Authorization: `Bearer ${auth.token}`,
30
      ...(options.body === undefined
31
        ? {}
32
        : { "Content-Type": "application/json" }),
33
      Accept: "application/json",
34
    },
35
    body: options.body === undefined ? undefined : JSON.stringify(options.body),
36
  })
37
  const text = await response.text()
38
  if (
39
    response.status === 401 ||
40
    response.status === 403 ||
41
    (!response.ok && text.includes("User is unauthorized"))
42
  ) {
43
    throw new Error(
44
      `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}`
45
    )
46
  }
47
  const data =
48
    text.trim() === ""
49
      ? {}
50
      : /^\s*[[{]/.test(text)
51
        ? JSON.parse(text)
52
        : undefined
53
  const error = findError(data)
54
  if (response.ok && data !== undefined && !error) return data
55
  const detail = error
56
    ? `${error.errorCode} ${error.errorMessage ?? (error.errors ?? []).join("; ")}`
57
    : `HTTP ${response.status} ${text.slice(0, 500)}`
58
  // Legacy lookups answer an unknown name or ID with an errorCode (cvprac
59
  // treats any error from getTaskById and getImageBundleByName that way).
60
  if (
61
    response.status === 404 ||
62
    (error && (options.optional || options.notFound))
63
  ) {
64
    if (options.optional) return undefined
65
    if (options.notFound) throw new Error(`${options.notFound} (${detail})`)
66
  }
67
  if (response.ok && !error) {
68
    throw new Error(
69
      `CloudVision returned a non-JSON response for ${url.pathname}: check that base_url is the CloudVision address. ${detail}`
70
    )
71
  }
72
  throw new Error(`CloudVision ${url.pathname} failed: ${detail}`)
73
}
74

75
function findError(value: any): any {
76
  if (!value || typeof value !== "object") return undefined
77
  // Like cvprac, only a truthy errorCode counts, at any depth.
78
  if (!Array.isArray(value) && value.errorCode) return value
79
  for (const item of Object.values(value)) {
80
    const error = findError(item)
81
    if (error) return error
82
  }
83
  return undefined
84
}
85