0

Create or update configlet

by
Published today

Create a static provisioning configlet, or replace the config of the one with that name (legacy Network Provisioning API). Updating a configlet that is applied somewhere makes CloudVision create pending tasks for the affected devices; the task IDs CloudVision reports are returned in taskIds (check List tasks if it is empty).

Script arista_cloudvision Verified

The script

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

3
/**
4
 * Create or update configlet
5
 * Create a static provisioning configlet, or replace the config of the one with that name (legacy Network Provisioning API). Updating a configlet that is applied somewhere makes CloudVision create pending tasks for the affected devices; the task IDs CloudVision reports are returned in taskIds (check List tasks if it is empty).
6
 */
7
export async function main(
8
  auth: RT.AristaCloudvision,
9
  name: string,
10
  config: string
11
) {
12
  const lookup = `/cvpservice/configlet/getConfigletByName.do?name=${encodeURIComponent(name)}`
13
  const existing = await cv(auth, lookup, { optional: true })
14
  if (!existing) {
15
    await cv(auth, "/cvpservice/configlet/addConfiglet.do", {
16
      body: { name, config },
17
    })
18
    const created = await cv(auth, lookup)
19
    return { action: "created", key: created.key, name, taskIds: [] }
20
  }
21
  if (existing.config === config) {
22
    return { action: "unchanged", key: existing.key, name, taskIds: [] }
23
  }
24
  const result = await cv(auth, "/cvpservice/configlet/updateConfiglet.do", {
25
    body: { config, key: existing.key, name, waitForTaskIds: true },
26
  })
27
  return {
28
    action: "updated",
29
    key: existing.key,
30
    name,
31
    taskIds: result.taskIds ?? [],
32
    response: result,
33
  }
34
}
35

36
// Calls CloudVision and turns its failures into readable errors. Legacy
37
// endpoints report errors as HTTP 200 with an errorCode field.
38
async function cv(
39
  auth: RT.AristaCloudvision,
40
  path: string,
41
  options: { body?: unknown; notFound?: string; optional?: boolean } = {}
42
): Promise<any> {
43
  const url = new URL(path, auth.base_url)
44
  // cvprac sends legacy endpoints to /cvpservice on CVaaS and /web on
45
  // on-premises CVP. Its is_cvaas flag is the resource's deployment field;
46
  // when that is unset, a host under arista.io is taken as CVaaS.
47
  const cvaas = auth.deployment
48
    ? auth.deployment === "cvaas"
49
    : /(^|\.)arista\.io\.?$/i.test(url.hostname)
50
  if (!cvaas && url.pathname.startsWith("/cvpservice/")) {
51
    url.pathname = url.pathname.replace(/^\/cvpservice\//, "/web/")
52
  }
53
  const response = await fetch(url, {
54
    method: options.body === undefined ? "GET" : "POST",
55
    headers: {
56
      Authorization: `Bearer ${auth.token}`,
57
      ...(options.body === undefined
58
        ? {}
59
        : { "Content-Type": "application/json" }),
60
      Accept: "application/json",
61
    },
62
    body: options.body === undefined ? undefined : JSON.stringify(options.body),
63
  })
64
  const text = await response.text()
65
  if (
66
    response.status === 401 ||
67
    response.status === 403 ||
68
    (!response.ok && text.includes("User is unauthorized"))
69
  ) {
70
    throw new Error(
71
      `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}`
72
    )
73
  }
74
  const data =
75
    text.trim() === ""
76
      ? {}
77
      : /^\s*[[{]/.test(text)
78
        ? JSON.parse(text)
79
        : undefined
80
  const error = findError(data)
81
  if (response.ok && data !== undefined && !error) return data
82
  const detail = error
83
    ? `${error.errorCode} ${error.errorMessage ?? (error.errors ?? []).join("; ")}`
84
    : `HTTP ${response.status} ${text.slice(0, 500)}`
85
  // Legacy lookups answer an unknown name or ID with an errorCode (cvprac
86
  // treats any error from getTaskById and getImageBundleByName that way).
87
  if (
88
    response.status === 404 ||
89
    (error && (options.optional || options.notFound))
90
  ) {
91
    if (options.optional) return undefined
92
    if (options.notFound) throw new Error(`${options.notFound} (${detail})`)
93
  }
94
  if (response.ok && !error) {
95
    throw new Error(
96
      `CloudVision returned a non-JSON response for ${url.pathname}: check that base_url is the CloudVision address. ${detail}`
97
    )
98
  }
99
  throw new Error(`CloudVision ${url.pathname} failed: ${detail}`)
100
}
101

102
function findError(value: any): any {
103
  if (!value || typeof value !== "object") return undefined
104
  // Like cvprac, only a truthy errorCode counts, at any depth.
105
  if (!Array.isArray(value) && value.errorCode) return value
106
  for (const item of Object.values(value)) {
107
    const error = findError(item)
108
    if (error) return error
109
  }
110
  return undefined
111
}
112