From 09e24dd92266665ca4e4cc7b41aa2284b8ecd740 Mon Sep 17 00:00:00 2001 From: Roy Razon Date: Tue, 6 Feb 2024 12:05:54 +0200 Subject: [PATCH] add azblob profile storage --- packages/cli-common/package.json | 2 + packages/cli-common/src/index.ts | 1 + packages/cli-common/src/prompts.ts | 37 ++++++++ packages/cli/src/fs.ts | 60 +++++++++++- packages/driver-azure/package.json | 4 +- packages/driver-azure/src/driver/index.ts | 40 +++++--- packages/driver-azure/src/fs.ts | 109 ++++++++++++++++++++++ packages/driver-azure/src/index.ts | 3 + packages/driver-azure/tsconfig.json | 1 + yarn.lock | 107 ++++++++++++++++++++- 10 files changed, 343 insertions(+), 21 deletions(-) create mode 100644 packages/cli-common/src/prompts.ts create mode 100644 packages/driver-azure/src/fs.ts diff --git a/packages/cli-common/package.json b/packages/cli-common/package.json index 741bdbce..c6ebdc60 100644 --- a/packages/cli-common/package.json +++ b/packages/cli-common/package.json @@ -12,6 +12,7 @@ ], "license": "Apache-2.0", "dependencies": { + "@inquirer/prompts": "^3.3.0", "@oclif/core": "^3.15.1", "@preevy/core": "0.0.60", "chalk": "^4.1.2", @@ -19,6 +20,7 @@ "lodash-es": "^4.17.21" }, "devDependencies": { + "@inquirer/type": "^1.2.0", "@jest/globals": "29.7.0", "@types/lodash-es": "^4.17.12", "@types/node": "18", diff --git a/packages/cli-common/src/index.ts b/packages/cli-common/src/index.ts index 4f483d91..8a11d3cf 100644 --- a/packages/cli-common/src/index.ts +++ b/packages/cli-common/src/index.ts @@ -8,3 +8,4 @@ export { export { formatFlagsToArgs, parseFlags, ParsedFlags } from './lib/flags.js' export { initHook } from './hooks/init/load-plugins.js' export { default as BaseCommand } from './commands/base-command.js' +export * as prompts from './prompts.js' diff --git a/packages/cli-common/src/prompts.ts b/packages/cli-common/src/prompts.ts new file mode 100644 index 00000000..b334070e --- /dev/null +++ b/packages/cli-common/src/prompts.ts @@ -0,0 +1,37 @@ +import * as inquirer from '@inquirer/prompts' +import chalk from 'chalk' + +const nullPrompt = inquirer.createPrompt( + (config, done) => { + const prefix = inquirer.usePrefix() + done(true) + return `${prefix} ${chalk.bold(config.message)} ${chalk.cyan(config.value)}` + }, +) + +export const selectOrSpecify = async ({ message, choices, specifyItem = '(specify)', specifyItemLocation = 'top' }: { + message: string + choices: { name: string; value: string }[] + specifyItem?: string + specifyItemLocation?: 'top' | 'bottom' +}) => { + const specify = () => inquirer.input({ message }, { clearPromptOnDone: true }) + const select = async () => ( + await inquirer.select({ + message, + choices: specifyItemLocation === 'top' ? [ + { name: specifyItem, value: undefined }, + new inquirer.Separator(), + ...choices, + ] : [ + ...choices, + new inquirer.Separator(), + { name: specifyItem, value: undefined }, + ], + loop: false, + }, { clearPromptOnDone: true }) + ) ?? await specify() + const result = choices.length ? await select() : await specify() + await nullPrompt({ message, value: result }) + return result +} diff --git a/packages/cli/src/fs.ts b/packages/cli/src/fs.ts index 237c748f..36de2498 100644 --- a/packages/cli/src/fs.ts +++ b/packages/cli/src/fs.ts @@ -1,8 +1,11 @@ import { fsTypeFromUrl, localFsFromUrl } from '@preevy/core' +import { prompts } from '@preevy/cli-common' import { googleCloudStorageFs, defaultBucketName as gsDefaultBucketName, defaultProjectId as defaultGceProjectId } from '@preevy/driver-gce' import { s3fs, defaultBucketName as s3DefaultBucketName, awsUtils, S3_REGIONS } from '@preevy/driver-lightsail' import * as inquirer from '@inquirer/prompts' +import * as azure from '@preevy/driver-azure' import inquirerAutoComplete from 'inquirer-autocomplete-standalone' +import { asyncFind, asyncTake, asyncToArray } from 'iter-tools-es' import { DriverName } from './drivers.js' import ambientAwsAccountId = awsUtils.ambientAccountId @@ -21,10 +24,15 @@ export const fsFromUrl = async (url: string, localBaseDir: string) => { // eslint-disable-next-line @typescript-eslint/return-await return await googleCloudStorageFs(url) } + if (fsType === 'azblob') { + // eslint false positive here on case-sensitive filesystems due to unknown type + // eslint-disable-next-line @typescript-eslint/return-await + return await azure.fs.azureBlobStorageFs(url) + } throw new Error(`Unsupported URL type: ${fsType}`) } -export const fsTypes = ['local', 's3', 'gs'] as const +export const fsTypes = ['local', 's3', 'gs', 'azblob'] as const export type FsType = typeof fsTypes[number] export const isFsType = (s: string): s is FsType => fsTypes.includes(s as FsType) @@ -35,6 +43,9 @@ const defaultFsType = (driver?: string): FsType => { if (driver as DriverName === 'gce') { return 'gs' } + if (driver as DriverName === 'azure') { + return 'azblob' + } return 'local' } @@ -45,6 +56,7 @@ export const chooseFsType = async ({ driver }: { driver?: string }) => await inq { value: 'local', name: 'local file' }, { value: 's3', name: 'AWS S3' }, { value: 'gs', name: 'Google Cloud Storage' }, + { value: 'azblob', name: 'Microsoft Azure Blob Storage' }, ], }) as FsType @@ -100,4 +112,50 @@ export const chooseFs: Record = { return `gs://${bucket}?project=${project}` }, + azblob: async ({ profileAlias, driver }: { + profileAlias: string + driver?: { name: DriverName; flags: Record } + }) => { + const subscriptionId = driver?.name === 'azure' + ? driver.flags['subscription-id'] as string + : await azure.inquireSubscriptionId().catch(() => undefined) + + const pageSize = 7 + + const accounts = subscriptionId + ? await asyncToArray(asyncTake(pageSize - 2, azure.fs.listStorageAccounts({ subscriptionId }))).catch(() => []) + : [] + + const account = await prompts.selectOrSpecify({ + message: 'Storage account name', + choices: accounts.map(({ name }) => ({ value: name, name })), + specifyItemLocation: 'bottom', + }) + + const inquireDomain = () => prompts.selectOrSpecify({ + message: 'Storage domain', + choices: [{ value: azure.fs.DEFAULT_DOMAIN, name: `(default): ${azure.fs.DEFAULT_DOMAIN}` }], + specifyItem: '(custom)', + specifyItemLocation: 'bottom', + }) + + const domain = (subscriptionId && accounts.length) + ? await (async () => { + const foundAccount = accounts.find(a => a.name === account) + ?? await asyncFind(({ name }) => name === account, azure.fs.listStorageAccounts({ subscriptionId })) + return foundAccount?.blobDomain ?? inquireDomain() + })() + : await inquireDomain() + + const container = await inquirer.input({ + message: 'Container name', + default: azure.fs.defaultContainerName({ profileAlias }), + }) + + return azure.fs.toUrl({ + container, + account, + domain: domain === azure.fs.DEFAULT_DOMAIN ? undefined : domain, + }) + }, } diff --git a/packages/driver-azure/package.json b/packages/driver-azure/package.json index 8e0b612c..b9d7d833 100644 --- a/packages/driver-azure/package.json +++ b/packages/driver-azure/package.json @@ -16,12 +16,14 @@ "@azure/arm-compute": "^20.0.0", "@azure/arm-network": "^30.2.0", "@azure/arm-resources": "^5.2.0", - "@azure/arm-storage": "^18.1.0", + "@azure/arm-storage": "^18.2.0", "@azure/arm-subscriptions": "^5.1.0", "@azure/identity": "^3.2.2", "@azure/logger": "^1.0.4", + "@azure/storage-blob": "^12.17.0", "@inquirer/prompts": "^3.3.0", "@oclif/core": "^3.15.1", + "@preevy/cli-common": "0.0.60", "@preevy/core": "0.0.60", "inquirer-autocomplete-standalone": "^0.8.1", "iter-tools-es": "^7.5.3", diff --git a/packages/driver-azure/src/driver/index.ts b/packages/driver-azure/src/driver/index.ts index 59153cfc..cd2c8742 100644 --- a/packages/driver-azure/src/driver/index.ts +++ b/packages/driver-azure/src/driver/index.ts @@ -1,6 +1,5 @@ import { Flags, Interfaces } from '@oclif/core' -import { asyncFirst, asyncMap } from 'iter-tools-es' -import * as inquirer from '@inquirer/prompts' +import { asyncMap, asyncToArray } from 'iter-tools-es' import inquirerAutoComplete from 'inquirer-autocomplete-standalone' import { InferredFlags } from '@oclif/core/lib/interfaces' import { Resource, VirtualMachine } from '@azure/arm-compute' @@ -22,6 +21,7 @@ import { Logger, machineStatusNodeExporterCommand, } from '@preevy/core' +import { prompts } from '@preevy/cli-common' import { pick } from 'lodash-es' import { Client, client as createClient, REGIONS } from './client.js' import { CUSTOMIZE_BARE_MACHINE } from './scripts.js' @@ -120,29 +120,41 @@ const flags = { required: true, }), 'subscription-id': Flags.string({ - description: 'Microsoft Azure subscription id', + description: 'Microsoft Azure Subscription ID', required: true, }), } as const type FlagTypes = Omit, 'json'> -const inquireFlags = async ({ log: _log }: { log: Logger }) => { - const region = await inquirerAutoComplete({ - message: flags.region.description as string, - source: async input => REGIONS.filter(r => !input || r.includes(input.toLowerCase())).map(value => ({ value })), - suggestOnly: true, - transformer: i => i.toLowerCase(), +export const inquireSubscriptionId = async (): Promise => { + const credential = new DefaultAzureCredential() + const subscriptionClient = new SubscriptionClient(credential) + const subscriptions = await asyncToArray(subscriptionClient.subscriptions.list()).catch(() => []) + return prompts.selectOrSpecify({ + message: 'Microsoft Azure Subscription ID', + choices: subscriptions.map(({ subscriptionId, displayName }) => ({ name: `${displayName} (${subscriptionId})`, value: subscriptionId as string })), + specifyItemLocation: 'bottom', }) +} +export const inquireRegion = async ({ subscriptionId }: { subscriptionId: string }): Promise => { const credential = new DefaultAzureCredential() const subscriptionClient = new SubscriptionClient(credential) - const defaultSubscriptionId = (await asyncFirst(subscriptionClient.subscriptions.list()))?.subscriptionId - - const subscriptionId = await inquirer.input({ - message: flags['subscription-id'].description as string, - default: defaultSubscriptionId, + const regions = await asyncToArray( + asyncMap(({ name }) => name as string, subscriptionClient.subscriptions.listLocations(subscriptionId)), + ).catch(() => REGIONS) + return await inquirerAutoComplete({ + message: flags.region.description as string, + source: async input => regions.filter(r => !input || r.includes(input.toLowerCase())).map(value => ({ value })), + suggestOnly: true, + transformer: i => i.toLowerCase(), }) +} + +const inquireFlags = async ({ log: _log }: { log: Logger }) => { + const subscriptionId = await inquireSubscriptionId() + const region = await inquireRegion({ subscriptionId }) return { region, 'subscription-id': subscriptionId } } diff --git a/packages/driver-azure/src/fs.ts b/packages/driver-azure/src/fs.ts new file mode 100644 index 00000000..ec11ee28 --- /dev/null +++ b/packages/driver-azure/src/fs.ts @@ -0,0 +1,109 @@ +import { DefaultAzureCredential } from '@azure/identity' +import { BlobServiceClient, RestError } from '@azure/storage-blob' +import { StorageManagementClient } from '@azure/arm-storage' +import { VirtualFS } from '@preevy/core' +import { asyncFilter, asyncMap } from 'iter-tools-es' +import { join } from 'path' + +export const DEFAULT_DOMAIN = 'blob.core.windows.net' as const + +export const parseUrl = (url: string, defaults: Partial<{ account: string; domain: string }> = {}) => { + const u = new URL(url) + if (u.protocol !== 'azblob:') { + throw new Error('Azure Blob Storage urls must start with azblob://') + } + + const account = u.searchParams.get('storage_account') ?? defaults?.account + if (!account) { + throw new Error(`Missing storage_account in url and no default storage account provided: ${url}`) + } + + return { + url: u, + container: u.hostname, + account, + path: u.pathname, + domain: u.searchParams.get('domain') ?? defaults?.domain ?? DEFAULT_DOMAIN, + } +} + +export const toUrl = ( + { account, domain, container, path }: { account?: string; domain?: string; container: string; path?: string }, +) => { + const u = new URL(`azblob://${container}`) + u.pathname = path ?? '/' + if (account) { + u.searchParams.set('storage_account', account) + } + if (domain) { + u.searchParams.set('domain', domain) + } + return u.toString() as `azblob://${string}` +} + +export const listContainers = ( + { account, domain }: { account: string; domain?: string }, +): AsyncIterable => { + const client = new BlobServiceClient(`https://${account}.${domain}`, new DefaultAzureCredential()) + const filtered = asyncFilter(({ deleted }) => !deleted, client.listContainers()) + return asyncMap(({ name }) => name, filtered) +} + +export const listStorageAccounts = ( + { subscriptionId }: { subscriptionId: string }, +): AsyncIterable<{ name: string; blobDomain?: string }> => { + const client = new StorageManagementClient(new DefaultAzureCredential(), subscriptionId) + return asyncMap( + ({ name, primaryEndpoints }) => ({ + name: name as string, + blobDomain: /(?<=\.)[^/]+/.exec(primaryEndpoints?.blob ?? '')?.toString() ?? undefined, + }), + client.storageAccounts.list(), + ) +} + +const isNotFoundError = (e: unknown): e is RestError => e instanceof RestError && e.statusCode === 404 +const isContainerNotFound = (e: unknown) => isNotFoundError(e) && e.code === 'ContainerNotFound' + +const catchNotFoundError = async (fn: () => Promise) => { + try { + return await fn() + } catch (e) { + if (isNotFoundError(e)) { + return undefined + } + throw e + } +} + +export const containerClient = (url: string) => { + const { container, account, path, domain } = parseUrl(url) + return { + client: new BlobServiceClient(`https://${account}.${domain}`, new DefaultAzureCredential()).getContainerClient(container), + path, + } +} + +export const azureBlobStorageFs = async (url: string): Promise => { + const { client, path } = containerClient(url) + await client.createIfNotExists() + + return { + read: async (filename: string) => { + const blob = client.getBlobClient(join(path, filename)) + return await catchNotFoundError(() => blob.downloadToBuffer()) + }, + write: async (filename: string, data: Buffer | string) => { + const blob = client.getBlockBlobClient(join(path, filename)) + await blob.upload(Buffer.isBuffer(data) ? data : Buffer.from(data), data.length) + }, + delete: async (filename: string) => { + const blob = client.getBlobClient(join(path, filename)) + await catchNotFoundError(() => blob.delete()) + }, + } +} + +export const defaultContainerName = ( + { profileAlias }: { profileAlias: string }, +) => ['preevy', profileAlias].join('-') diff --git a/packages/driver-azure/src/index.ts b/packages/driver-azure/src/index.ts index ebc12ed4..6ec17e14 100644 --- a/packages/driver-azure/src/index.ts +++ b/packages/driver-azure/src/index.ts @@ -1,3 +1,6 @@ import azure from './driver/index.js' +export * as fs from './fs.js' +export { inquireSubscriptionId, inquireRegion } from './driver/index.js' + export default azure diff --git a/packages/driver-azure/tsconfig.json b/packages/driver-azure/tsconfig.json index 9267c46b..685d7cce 100644 --- a/packages/driver-azure/tsconfig.json +++ b/packages/driver-azure/tsconfig.json @@ -33,5 +33,6 @@ "references": [ { "path": "../common" }, { "path": "../core" }, + { "path": "../cli-common" }, ] } diff --git a/yarn.lock b/yarn.lock index 0cb0fabb..fdfc9351 100644 --- a/yarn.lock +++ b/yarn.lock @@ -718,6 +718,13 @@ dependencies: tslib "^2.2.0" +"@azure/abort-controller@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.0.0.tgz#a66d26c7f64977e3ff4b9e0b136296cb4bd47e8b" + integrity sha512-RP/mR/WJchR+g+nQFJGOec+nzeN/VvjlwbinccoqfhTsTHbb8X5+mLDp48kHT0ueyum0BNSwGm0kX0UZuIqTGg== + dependencies: + tslib "^2.2.0" + "@azure/arm-compute@^20.0.0": version "20.0.0" resolved "https://registry.yarnpkg.com/@azure/arm-compute/-/arm-compute-20.0.0.tgz#ae5dbb1abbe664e7977c9ca7d64a15fff7000a05" @@ -757,9 +764,9 @@ "@azure/core-rest-pipeline" "^1.8.0" tslib "^2.2.0" -"@azure/arm-storage@^18.1.0": +"@azure/arm-storage@^18.2.0": version "18.2.0" - resolved "https://registry.yarnpkg.com/@azure/arm-storage/-/arm-storage-18.2.0.tgz#568ff37b3ae4cc7fc3bd873b193fdec688dd38eb" + resolved "https://registry.npmjs.org/@azure/arm-storage/-/arm-storage-18.2.0.tgz#568ff37b3ae4cc7fc3bd873b193fdec688dd38eb" integrity sha512-jLUsAVFq5YBOYQfhE6L+KaUs+lntctKVafPz50FFScfzPMHYT/SptQGZ7BKzAJBBE/evdt8afmt2NSdl8Szomg== dependencies: "@azure/abort-controller" "^1.0.0" @@ -772,7 +779,7 @@ "@azure/arm-subscriptions@^5.1.0": version "5.1.0" - resolved "https://registry.yarnpkg.com/@azure/arm-subscriptions/-/arm-subscriptions-5.1.0.tgz#5598d6f40d1e47ccc5a4c9fff758c5fac02261fa" + resolved "https://registry.npmjs.org/@azure/arm-subscriptions/-/arm-subscriptions-5.1.0.tgz#5598d6f40d1e47ccc5a4c9fff758c5fac02261fa" integrity sha512-6BeOF2eQWNLq22ch7xP9RxYnPjtGev54OUCGggKOWoOvmesK7jUZbIyLk8JeXDT21PEl7iyYnxw78gxJ7zBxQw== dependencies: "@azure/abort-controller" "^1.0.0" @@ -805,6 +812,26 @@ "@azure/logger" "^1.0.0" tslib "^2.2.0" +"@azure/core-http@^3.0.0": + version "3.0.4" + resolved "https://registry.npmjs.org/@azure/core-http/-/core-http-3.0.4.tgz#024b2909bbc0f2fce08c74f97a21312c4f42e922" + integrity sha512-Fok9VVhMdxAFOtqiiAtg74fL0UJkt0z3D+ouUUxcRLzZNBioPRAMJFVxiWoJljYpXsRi4GDQHzQHDc9AiYaIUQ== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-auth" "^1.3.0" + "@azure/core-tracing" "1.0.0-preview.13" + "@azure/core-util" "^1.1.1" + "@azure/logger" "^1.0.0" + "@types/node-fetch" "^2.5.0" + "@types/tunnel" "^0.0.3" + form-data "^4.0.0" + node-fetch "^2.6.7" + process "^0.11.10" + tslib "^2.2.0" + tunnel "^0.0.6" + uuid "^8.3.0" + xml2js "^0.5.0" + "@azure/core-lro@^2.2.0", "@azure/core-lro@^2.5.0", "@azure/core-lro@^2.5.3": version "2.5.4" resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-2.5.4.tgz#b21e2bcb8bd9a8a652ff85b61adeea51a8055f90" @@ -815,7 +842,7 @@ "@azure/logger" "^1.0.0" tslib "^2.2.0" -"@azure/core-paging@^1.2.0": +"@azure/core-paging@^1.1.1", "@azure/core-paging@^1.2.0": version "1.5.0" resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.5.0.tgz#5a5b09353e636072e6a7fc38f7879e11d0afb15f" integrity sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw== @@ -837,6 +864,14 @@ https-proxy-agent "^5.0.0" tslib "^2.2.0" +"@azure/core-tracing@1.0.0-preview.13": + version "1.0.0-preview.13" + resolved "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz#55883d40ae2042f6f1e12b17dd0c0d34c536d644" + integrity sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ== + dependencies: + "@opentelemetry/api" "^1.0.1" + tslib "^2.2.0" + "@azure/core-tracing@^1.0.0", "@azure/core-tracing@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.0.1.tgz#352a38cbea438c4a83c86b314f48017d70ba9503" @@ -852,6 +887,14 @@ "@azure/abort-controller" "^1.0.0" tslib "^2.2.0" +"@azure/core-util@^1.1.1": + version "1.7.0" + resolved "https://registry.npmjs.org/@azure/core-util/-/core-util-1.7.0.tgz#3a2f73e8c7eed0666e8b6ff9ca2c1951e175feba" + integrity sha512-Zq2i3QO6k9DA8vnm29mYM4G8IE9u1mhF1GUabVEqPNX8Lj833gdxQ2NAFxt2BZsfAL+e9cT8SyVN7dFVJ/Hf0g== + dependencies: + "@azure/abort-controller" "^2.0.0" + tslib "^2.2.0" + "@azure/identity@^3.2.2": version "3.4.1" resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-3.4.1.tgz#18ba48b7421c818ef8116e8eec3c03ec1a62649a" @@ -900,6 +943,20 @@ jsonwebtoken "^9.0.0" uuid "^8.3.0" +"@azure/storage-blob@^12.17.0": + version "12.17.0" + resolved "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.17.0.tgz#04aad7f59cb08dbbe5b1b672a9f5b6256c8c9006" + integrity sha512-sM4vpsCpcCApagRW5UIjQNlNylo02my2opgp0Emi8x888hZUvJ3dN69Oq20cEGXkMUWnoCrBaB0zyS3yeB87sQ== + dependencies: + "@azure/abort-controller" "^1.0.0" + "@azure/core-http" "^3.0.0" + "@azure/core-lro" "^2.2.0" + "@azure/core-paging" "^1.1.1" + "@azure/core-tracing" "1.0.0-preview.13" + "@azure/logger" "^1.0.0" + events "^3.0.0" + tslib "^2.2.0" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" @@ -1851,6 +1908,11 @@ resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-1.1.5.tgz#b8c171f755859c8159b10e41e1e3a88f0ca99d7f" integrity sha512-wmwHvHozpPo4IZkkNtbYenem/0wnfI6hvOcGKmPEa0DwuaH5XUQzFqy6OpEpjEegZMhYIk8HDYITI16BPLtrRA== +"@inquirer/type@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@inquirer/type/-/type-1.2.0.tgz#a569613628a881c2104289ca868a7def54e5c49d" + integrity sha512-/vvkUkYhrjbm+RolU7V1aUFDydZVKNKqKHR5TsE+j5DXgXFwrsOPcoGUJ02K0O7q7O53CU2DOTMYCHeGZ25WHA== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -3096,6 +3158,11 @@ "@octokit/webhooks-types" "7.1.0" aggregate-error "^3.1.0" +"@opentelemetry/api@^1.0.1": + version "1.7.0" + resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.7.0.tgz#b139c81999c23e3c8d3c0a7234480e945920fc40" + integrity sha512-AdY5wvN0P2vXBi3b29hxZgSFvdhdxPB9+f0B6s//P9Q8nibRWeA3cHm8UmLpio9ABigkVHJ5NMPk+Mz8VCCyrw== + "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" @@ -4075,7 +4142,7 @@ dependencies: "@types/node" "*" -"@types/node-fetch@^2.6.3": +"@types/node-fetch@^2.5.0", "@types/node-fetch@^2.6.3": version "2.6.11" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" integrity sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g== @@ -4227,6 +4294,13 @@ resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== +"@types/tunnel@^0.0.3": + version "0.0.3" + resolved "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz#f109e730b072b3136347561fc558c9358bb8c6e9" + integrity sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA== + dependencies: + "@types/node" "*" + "@types/vinyl@^2.0.4": version "2.0.7" resolved "https://registry.yarnpkg.com/@types/vinyl/-/vinyl-2.0.7.tgz#9739a9a2afaf9af32761c54a0e82c735279f726c" @@ -12163,6 +12237,11 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sax@>=0.6.0: + version "1.3.0" + resolved "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" + integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== + scoped-regex@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/scoped-regex/-/scoped-regex-2.1.0.tgz#7b9be845d81fd9d21d1ec97c61a0b7cf86d2015f" @@ -13174,6 +13253,11 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" +tunnel@^0.0.6: + version "0.0.6" + resolved "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz#72f1314b34a5b192db012324df2cc587ca47f92c" + integrity sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg== + tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" @@ -13731,6 +13815,19 @@ ws@^8.0.0, ws@^8.11.0, ws@^8.13.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4" integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ== +xml2js@^0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz#d9440631fbb2ed800203fad106f2724f62c493b7" + integrity sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA== + dependencies: + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== + xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"