From d0a4a4242d410967fe67387262bc650bcbb2bb1e Mon Sep 17 00:00:00 2001 From: Ronald Quan Date: Mon, 25 Feb 2019 15:09:22 -0800 Subject: [PATCH] Feature/mssql-big-data-cluster (#4107) * Adding kubernetes installer. * Adding variety of kubectl support and integrating into the kubeconfig target cluster page. * Addressing PR comments, refactored utility file locations and added missing license headers. --- extensions/big-data-cluster/package.json | 53 +- .../big-data-cluster/src/config/config.ts | 121 ++++ .../src/installer/download.ts | 45 ++ .../src/installer/installer.ts | 69 ++ extensions/big-data-cluster/src/interfaces.ts | 21 +- .../big-data-cluster/src/kubectl/binutil.ts | 117 ++++ .../src/kubectl/compatibility.ts | 66 ++ .../big-data-cluster/src/kubectl/host.ts | 42 ++ .../src/kubectl/kubeChannel.ts | 26 + .../big-data-cluster/src/kubectl/kubectl.ts | 130 ++++ .../src/kubectl/kubectlUtils.ts | 86 +++ extensions/big-data-cluster/src/main.ts | 39 +- .../big-data-cluster/src/mainController.ts | 8 +- extensions/big-data-cluster/src/utility/fs.ts | 65 ++ .../big-data-cluster/src/utility/shell.ts | 204 ++++++ .../create-cluster/createClusterModel.ts | 10 +- .../create-cluster/createClusterWizard.ts | 7 +- .../pages/selectExistingClusterPage.ts | 11 +- extensions/big-data-cluster/yarn.lock | 599 ++++++++++++++++++ 19 files changed, 1701 insertions(+), 18 deletions(-) create mode 100644 extensions/big-data-cluster/src/config/config.ts create mode 100644 extensions/big-data-cluster/src/installer/download.ts create mode 100644 extensions/big-data-cluster/src/installer/installer.ts create mode 100644 extensions/big-data-cluster/src/kubectl/binutil.ts create mode 100644 extensions/big-data-cluster/src/kubectl/compatibility.ts create mode 100644 extensions/big-data-cluster/src/kubectl/host.ts create mode 100644 extensions/big-data-cluster/src/kubectl/kubeChannel.ts create mode 100644 extensions/big-data-cluster/src/kubectl/kubectl.ts create mode 100644 extensions/big-data-cluster/src/kubectl/kubectlUtils.ts create mode 100644 extensions/big-data-cluster/src/utility/fs.ts create mode 100644 extensions/big-data-cluster/src/utility/shell.ts diff --git a/extensions/big-data-cluster/package.json b/extensions/big-data-cluster/package.json index b02eea39f4..145726b5c2 100644 --- a/extensions/big-data-cluster/package.json +++ b/extensions/big-data-cluster/package.json @@ -23,6 +23,56 @@ "Microsoft.mssql" ], "contributes": { + "configuration": { + "type": "object", + "title": "Kubernetes configuration", + "properties": { + "mssql-bdc": { + "type": "object", + "description": "Kubernetes configuration", + "properties": { + "mssql-bdc.kubectl-path": { + "type": "string", + "description": "File path to a kubectl binary." + }, + "mssql-bdc.kubectl-path.windows": { + "type": "string", + "description": "File path to a kubectl binary." + }, + "mssql-bdc.kubectl-path.mac": { + "type": "string", + "description": "File path to a kubectl binary." + }, + "mssql-bdc.kubectl-path.linux": { + "type": "string", + "description": "File path to a kubectl binary." + }, + "mssql-bdc.kubeconfig": { + "type": "string", + "description": "File path to the kubeconfig file." + }, + "mssql-bdc.knownKubeconfigs": { + "type": "array", + "description": "File paths to kubeconfig files from which you can select." + }, + "mssql-bdc.outputFormat": { + "enum": [ + "json", + "yaml" + ], + "type": "string", + "description": "Output format for Kubernetes specs. One of 'json' or 'yaml' (default)." + } + }, + "default": { + "mssql-bdc.namespace": "", + "mssql-bdc.kubectl-path": "", + "mssql-bdc.kubeconfig": "", + "mssql-bdc.knownKubeconfigs": [] + } + } + } + }, "commands": [ { "command": "mssql.cluster.create", @@ -32,7 +82,8 @@ ] }, "dependencies": { - "vscode-nls": "^3.2.1" + "vscode-nls": "^3.2.1", + "download": "^6.2.5" }, "devDependencies": { "mocha-junit-reporter": "^1.17.0", diff --git a/extensions/big-data-cluster/src/config/config.ts b/extensions/big-data-cluster/src/config/config.ts new file mode 100644 index 0000000000..a81ce4a4eb --- /dev/null +++ b/extensions/big-data-cluster/src/config/config.ts @@ -0,0 +1,121 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import { Host } from '../kubectl/host'; +import { Shell, Platform } from '../utility/shell'; + +const EXTENSION_CONFIG_KEY = "mssql-bdc"; +const KUBECONFIG_PATH_KEY = "mssql-bdc.kubeconfig"; +const KNOWN_KUBECONFIGS_KEY = "mssql-bdc.knownKubeconfigs"; + +export async function addPathToConfig(configKey: string, value: string): Promise { + await setConfigValue(configKey, value); +} + +async function setConfigValue(configKey: string, value: any): Promise { + await atAllConfigScopes(addValueToConfigAtScope, configKey, value); +} + +async function addValueToConfigAtScope(configKey: string, value: any, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise { + if (!createIfNotExist) { + if (!valueAtScope || !(valueAtScope[configKey])) { + return; + } + } + + let newValue: any = {}; + if (valueAtScope) { + newValue = Object.assign({}, valueAtScope); + } + newValue[configKey] = value; + await vscode.workspace.getConfiguration().update(EXTENSION_CONFIG_KEY, newValue, scope); +} + +async function addValueToConfigArray(configKey: string, value: string): Promise { + await atAllConfigScopes(addValueToConfigArrayAtScope, configKey, value); +} + +async function addValueToConfigArrayAtScope(configKey: string, value: string, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise { + if (!createIfNotExist) { + if (!valueAtScope || !(valueAtScope[configKey])) { + return; + } + } + + let newValue: any = {}; + if (valueAtScope) { + newValue = Object.assign({}, valueAtScope); + } + const arrayEntry: string[] = newValue[configKey] || []; + arrayEntry.push(value); + newValue[configKey] = arrayEntry; + await vscode.workspace.getConfiguration().update(EXTENSION_CONFIG_KEY, newValue, scope); +} + +type ConfigUpdater = (configKey: string, value: T, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean) => Promise; + +async function atAllConfigScopes(fn: ConfigUpdater, configKey: string, value: T): Promise { + const config = vscode.workspace.getConfiguration().inspect(EXTENSION_CONFIG_KEY)!; + await fn(configKey, value, vscode.ConfigurationTarget.Global, config.globalValue, true); + await fn(configKey, value, vscode.ConfigurationTarget.Workspace, config.workspaceValue, false); + await fn(configKey, value, vscode.ConfigurationTarget.WorkspaceFolder, config.workspaceFolderValue, false); +} + +// Functions for working with the list of known kubeconfigs + +export function getKnownKubeconfigs(): string[] { + const kkcConfig = vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)[KNOWN_KUBECONFIGS_KEY]; + if (!kkcConfig || !kkcConfig.length) { + return []; + } + return kkcConfig as string[]; +} + +export async function addKnownKubeconfig(kubeconfigPath: string) { + await addValueToConfigArray(KNOWN_KUBECONFIGS_KEY, kubeconfigPath); +} + +// Functions for working with the active kubeconfig setting + +export async function setActiveKubeconfig(kubeconfig: string): Promise { + await addPathToConfig(KUBECONFIG_PATH_KEY, kubeconfig); +} + +export function getActiveKubeconfig(): string { + return vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)[KUBECONFIG_PATH_KEY]; +} + +// Functions for working with tool paths + +export function getToolPath(host: Host, shell: Shell, tool: string): string | undefined { + const baseKey = toolPathBaseKey(tool); + return getPathSetting(host, shell, baseKey); +} + +function getPathSetting(host: Host, shell: Shell, baseKey: string): string | undefined { + const os = shell.platform(); + const osOverridePath = host.getConfiguration(EXTENSION_CONFIG_KEY)[osOverrideKey(os, baseKey)]; + return osOverridePath || host.getConfiguration(EXTENSION_CONFIG_KEY)[baseKey]; +} + +export function toolPathBaseKey(tool: string): string { + return `mssql-bdc.${tool}-path`; +} + +function osOverrideKey(os: Platform, baseKey: string): string { + const osKey = osKeyString(os); + return osKey ? `${baseKey}.${osKey}` : baseKey; // The 'else' clause should never happen so don't worry that this would result in double-checking a missing base key +} + +function osKeyString(os: Platform): string | null { + switch (os) { + case Platform.Windows: return 'windows'; + case Platform.MacOS: return 'mac'; + case Platform.Linux: return 'linux'; + default: return null; + } +} + diff --git a/extensions/big-data-cluster/src/installer/download.ts b/extensions/big-data-cluster/src/installer/download.ts new file mode 100644 index 0000000000..42bdd4e40a --- /dev/null +++ b/extensions/big-data-cluster/src/installer/download.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as path from 'path'; +import * as stream from 'stream'; +import * as tmp from 'tmp'; + +import { succeeded, Errorable } from '../interfaces'; + +type DownloadFunc = + (url: string, destination?: string, options?: any) + => Promise & stream.Duplex; // Stream has additional events - see https://www.npmjs.com/package/download + +let download: DownloadFunc; + +function ensureDownloadFunc() { + if (!download) { + const home = process.env['HOME']; + download = require('download'); + if (home) { + process.env['HOME'] = home; + } + } +} + +export async function toTempFile(sourceUrl: string): Promise> { + const tempFileObj = tmp.fileSync({ prefix: "mssql-bdc-autoinstall-" }); + const downloadResult = await to(sourceUrl, tempFileObj.name); + if (succeeded(downloadResult)) { + return { succeeded: true, result: tempFileObj.name }; + } + return { succeeded: false, error: downloadResult.error }; +} + +export async function to(sourceUrl: string, destinationFile: string): Promise> { + ensureDownloadFunc(); + try { + await download(sourceUrl, path.dirname(destinationFile), { filename: path.basename(destinationFile) }); + return { succeeded: true, result: null }; + } catch (e) { + return { succeeded: false, error: [e.message] }; + } +} diff --git a/extensions/big-data-cluster/src/installer/installer.ts b/extensions/big-data-cluster/src/installer/installer.ts new file mode 100644 index 0000000000..0587de5280 --- /dev/null +++ b/extensions/big-data-cluster/src/installer/installer.ts @@ -0,0 +1,69 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as download from './download'; +import * as fs from 'fs'; +import mkdirp = require('mkdirp'); +import * as path from 'path'; +import { Shell, Platform } from '../utility/shell'; +import { Errorable, failed } from '../interfaces'; +import { addPathToConfig, toolPathBaseKey } from '../config/config'; + +export async function installKubectl(shell: Shell): Promise> { + const tool = 'kubectl'; + const binFile = (shell.isUnix()) ? 'kubectl' : 'kubectl.exe'; + const os = platformUrlString(shell.platform()); + + const version = await getStableKubectlVersion(); + if (failed(version)) { + return { succeeded: false, error: version.error }; + } + + const installFolder = getInstallFolder(shell, tool); + mkdirp.sync(installFolder); + + const kubectlUrl = `https://storage.googleapis.com/kubernetes-release/release/${version.result.trim()}/bin/${os}/amd64/${binFile}`; + const downloadFile = path.join(installFolder, binFile); + const downloadResult = await download.to(kubectlUrl, downloadFile); + if (failed(downloadResult)) { + return { succeeded: false, error: [`Failed to download kubectl: ${downloadResult.error[0]}`] }; + } + + if (shell.isUnix()) { + fs.chmodSync(downloadFile, '0777'); + } + + await addPathToConfig(toolPathBaseKey(tool), downloadFile); + return { succeeded: true, result: null }; +} + +async function getStableKubectlVersion(): Promise> { + const downloadResult = await download.toTempFile('https://storage.googleapis.com/kubernetes-release/release/stable.txt'); + if (failed(downloadResult)) { + return { succeeded: false, error: [`Failed to establish kubectl stable version: ${downloadResult.error[0]}`] }; + } + const version = fs.readFileSync(downloadResult.result, 'utf-8'); + fs.unlinkSync(downloadResult.result); + return { succeeded: true, result: version }; +} + +export function getInstallFolder(shell: Shell, tool: string): string { + return path.join(shell.home(), `.mssql-bdc/tools/${tool}`); +} + +function platformUrlString(platform: Platform, supported?: Platform[]): string | null { + if (supported && supported.indexOf(platform) < 0) { + return null; + } + switch (platform) { + case Platform.Windows: return 'windows'; + case Platform.MacOS: return 'darwin'; + case Platform.Linux: return 'linux'; + default: return null; + } +} + + diff --git a/extensions/big-data-cluster/src/interfaces.ts b/extensions/big-data-cluster/src/interfaces.ts index 37d352d2d7..6471c1ef03 100644 --- a/extensions/big-data-cluster/src/interfaces.ts +++ b/extensions/big-data-cluster/src/interfaces.ts @@ -15,6 +15,25 @@ export enum TargetClusterType { NewAksCluster } +export interface Succeeded { + readonly succeeded: true; + readonly result: T; +} + +export interface Failed { + readonly succeeded: false; + readonly error: string[]; +} + +export type Errorable = Succeeded | Failed; + +export function succeeded(e: Errorable): e is Succeeded { + return e.succeeded; +} + +export function failed(e: Errorable): e is Failed { + return !e.succeeded; +} export interface ClusterPorts { sql: string; knox: string; @@ -43,4 +62,4 @@ export interface ToolInfo { name: string, description: string, isInstalled: boolean -} \ No newline at end of file +} diff --git a/extensions/big-data-cluster/src/kubectl/binutil.ts b/extensions/big-data-cluster/src/kubectl/binutil.ts new file mode 100644 index 0000000000..be7699de9f --- /dev/null +++ b/extensions/big-data-cluster/src/kubectl/binutil.ts @@ -0,0 +1,117 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Shell } from '../utility/shell'; +import { Host } from './host'; +import { FS } from '../utility/fs'; + +export interface BinCheckContext { + readonly host: Host; + readonly fs: FS; + readonly shell: Shell; + readonly installDependenciesCallback: () => void; + binFound: boolean; + binPath: string; +} + +interface FindBinaryResult { + err: number | null; + output: string; +} + +async function findBinary(shell: Shell, binName: string): Promise { + let cmd = `which ${binName}`; + + if (shell.isWindows()) { + cmd = `where.exe ${binName}.exe`; + } + + const opts = { + async: true, + env: { + HOME: process.env.HOME, + PATH: process.env.PATH + } + }; + + const execResult = await shell.execCore(cmd, opts); + if (execResult.code) { + return { err: execResult.code, output: execResult.stderr }; + } + + return { err: null, output: execResult.stdout }; +} + +export function execPath(shell: Shell, basePath: string): string { + let bin = basePath; + if (shell.isWindows() && bin && !(bin.endsWith('.exe'))) { + bin = bin + '.exe'; + } + return bin; +} + +type CheckPresentFailureReason = 'inferFailed' | 'configuredFileMissing'; + +function alertNoBin(host: Host, binName: string, failureReason: CheckPresentFailureReason, message: string, installDependencies: () => void): void { + switch (failureReason) { + case 'inferFailed': + host.showErrorMessage(message, 'Install dependencies', 'Learn more').then( + (str) => { + switch (str) { + case 'Learn more': + host.showInformationMessage(`Add ${binName} directory to path, or set "mssql-bdc.${binName}-path" config to ${binName} binary.`); + break; + case 'Install dependencies': + installDependencies(); + break; + } + + } + ); + break; + case 'configuredFileMissing': + host.showErrorMessage(message, 'Install dependencies').then( + (str) => { + if (str === 'Install dependencies') { + installDependencies(); + } + } + ); + break; + } +} + +export async function checkForBinary(context: BinCheckContext, bin: string | undefined, binName: string, inferFailedMessage: string, configuredFileMissingMessage: string, alertOnFail: boolean): Promise { + if (!bin) { + const fb = await findBinary(context.shell, binName); + + if (fb.err || fb.output.length === 0) { + if (alertOnFail) { + alertNoBin(context.host, binName, 'inferFailed', inferFailedMessage, context.installDependenciesCallback); + } + return false; + } + + context.binFound = true; + + return true; + } + + if (context.shell.isWindows) { + context.binFound = context.fs.existsSync(bin); + } else { + const sr = await context.shell.exec(`ls ${bin}`); + context.binFound = (!!sr && sr.code === 0); + } + if (context.binFound) { + context.binPath = bin; + } else { + if (alertOnFail) { + alertNoBin(context.host, binName, 'configuredFileMissing', configuredFileMissingMessage, context.installDependenciesCallback); + } + } + + return context.binFound; +} diff --git a/extensions/big-data-cluster/src/kubectl/compatibility.ts b/extensions/big-data-cluster/src/kubectl/compatibility.ts new file mode 100644 index 0000000000..3248ca0bcb --- /dev/null +++ b/extensions/big-data-cluster/src/kubectl/compatibility.ts @@ -0,0 +1,66 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Errorable, failed } from '../interfaces'; + +interface CompatibilityGuaranteed { + readonly guaranteed: true; +} + +interface CompatibilityNotGuaranteed { + readonly guaranteed: false; + readonly didCheck: boolean; + readonly clientVersion: string; + readonly serverVersion: string; +} + +export type Compatibility = CompatibilityGuaranteed | CompatibilityNotGuaranteed; + +export function isGuaranteedCompatible(c: Compatibility): c is CompatibilityGuaranteed { + return c.guaranteed; +} + +export interface Version { + readonly major: string; + readonly minor: string; + readonly gitVersion: string; +} + +export async function check(kubectlLoadJSON: (cmd: string) => Promise>): Promise { + const version = await kubectlLoadJSON('version -o json'); + if (failed(version)) { + return { + guaranteed: false, + didCheck: false, + clientVersion: '', + serverVersion: '' + }; + } + + const clientVersion: Version = version.result.clientVersion; + const serverVersion: Version = version.result.serverVersion; + + if (isCompatible(clientVersion, serverVersion)) { + return { guaranteed: true }; + } + + return { + guaranteed: false, + didCheck: true, + clientVersion: clientVersion.gitVersion, + serverVersion: serverVersion.gitVersion + }; +} + +function isCompatible(clientVersion: Version, serverVersion: Version): boolean { + if (clientVersion.major === serverVersion.major) { + const clientMinor = Number.parseInt(clientVersion.minor); + const serverMinor = Number.parseInt(serverVersion.minor); + if (Number.isInteger(clientMinor) && Number.isInteger(serverMinor) && Math.abs(clientMinor - serverMinor) <= 1) { + return true; + } + } + return false; +} diff --git a/extensions/big-data-cluster/src/kubectl/host.ts b/extensions/big-data-cluster/src/kubectl/host.ts new file mode 100644 index 0000000000..9e0d15e369 --- /dev/null +++ b/extensions/big-data-cluster/src/kubectl/host.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; + +export interface Host { + showErrorMessage(message: string, ...items: string[]): Thenable; + showWarningMessage(message: string, ...items: string[]): Thenable; + showInformationMessage(message: string, ...items: string[]): Thenable; + getConfiguration(key: string): any; + onDidChangeConfiguration(listener: (ch: vscode.ConfigurationChangeEvent) => any): vscode.Disposable; +} + +export const host: Host = { + showErrorMessage : showErrorMessage, + showWarningMessage : showWarningMessage, + showInformationMessage : showInformationMessage, + getConfiguration : getConfiguration, + onDidChangeConfiguration : onDidChangeConfiguration, +}; + +function showErrorMessage(message: string, ...items: string[]): Thenable { + return vscode.window.showErrorMessage(message, ...items); +} + +function showWarningMessage(message: string, ...items: string[]): Thenable { + return vscode.window.showWarningMessage(message, ...items); +} + +function showInformationMessage(message: string, ...items: string[]): Thenable { + return vscode.window.showInformationMessage(message, ...items); +} + +function getConfiguration(key: string): any { + return vscode.workspace.getConfiguration(key); +} + +function onDidChangeConfiguration(listener: (e: vscode.ConfigurationChangeEvent) => any): vscode.Disposable { + return vscode.workspace.onDidChangeConfiguration(listener); +} diff --git a/extensions/big-data-cluster/src/kubectl/kubeChannel.ts b/extensions/big-data-cluster/src/kubectl/kubeChannel.ts new file mode 100644 index 0000000000..31f1933ee1 --- /dev/null +++ b/extensions/big-data-cluster/src/kubectl/kubeChannel.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from "vscode"; + +export interface ISqlServerBigDataClusterChannel { + showOutput(message: any, title?: string): void; +} + +class SqlServerBigDataCluster implements ISqlServerBigDataClusterChannel { + private readonly channel: vscode.OutputChannel = vscode.window.createOutputChannel("SQL Server big data cluster"); + + showOutput(message: any, title?: string): void { + if (title) { + const simplifiedTime = (new Date()).toISOString().replace(/z|t/gi, ' ').trim(); // YYYY-MM-DD HH:mm:ss.sss + const hightlightingTitle = `[${title} ${simplifiedTime}]`; + this.channel.appendLine(hightlightingTitle); + } + this.channel.appendLine(message); + this.channel.show(); + } +} + +export const sqlserverbigdataclusterchannel: ISqlServerBigDataClusterChannel = new SqlServerBigDataCluster(); diff --git a/extensions/big-data-cluster/src/kubectl/kubectl.ts b/extensions/big-data-cluster/src/kubectl/kubectl.ts new file mode 100644 index 0000000000..78b52f9f21 --- /dev/null +++ b/extensions/big-data-cluster/src/kubectl/kubectl.ts @@ -0,0 +1,130 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Host } from './host'; +import { FS } from '../utility/fs'; +import { Shell, ShellResult } from '../utility/shell'; +import * as binutil from './binutil'; +import { Errorable } from '../interfaces'; +import * as compatibility from './compatibility'; +import { getToolPath } from '../config/config'; + +export interface Kubectl { + checkPresent(errorMessageMode: CheckPresentMessageMode): Promise; + asJson(command: string): Promise>; +} + +interface Context { + readonly host: Host; + readonly fs: FS; + readonly shell: Shell; + readonly installDependenciesCallback: () => void; + binFound: boolean; + binPath: string; +} + +class KubectlImpl implements Kubectl { + constructor(host: Host, fs: FS, shell: Shell, installDependenciesCallback: () => void, kubectlFound: boolean) { + this.context = { host : host, fs : fs, shell : shell, installDependenciesCallback : installDependenciesCallback, binFound : kubectlFound, binPath : 'kubectl' }; + } + + private readonly context: Context; + + checkPresent(errorMessageMode: CheckPresentMessageMode): Promise { + return checkPresent(this.context, errorMessageMode); + } + asJson(command: string): Promise> { + return asJson(this.context, command); + } +} + +export function create(host: Host, fs: FS, shell: Shell, installDependenciesCallback: () => void): Kubectl { + return new KubectlImpl(host, fs, shell, installDependenciesCallback, false); +} + +export enum CheckPresentMessageMode { + Command, + Activation, + Silent, +} + +async function checkPresent(context: Context, errorMessageMode: CheckPresentMessageMode): Promise { + if (context.binFound) { + return true; + } + + return await checkForKubectlInternal(context, errorMessageMode); +} + +async function checkForKubectlInternal(context: Context, errorMessageMode: CheckPresentMessageMode): Promise { + const binName = 'kubectl'; + const bin = getToolPath(context.host, context.shell, binName); + + const contextMessage = getCheckKubectlContextMessage(errorMessageMode); + const inferFailedMessage = `Could not find "${binName}" binary.${contextMessage}`; + const configuredFileMissingMessage = `${bin} is not installed. ${contextMessage}`; + + return await binutil.checkForBinary(context, bin, binName, inferFailedMessage, configuredFileMissingMessage, errorMessageMode !== CheckPresentMessageMode.Silent); +} + +function getCheckKubectlContextMessage(errorMessageMode: CheckPresentMessageMode): string { + if (errorMessageMode === CheckPresentMessageMode.Activation) { + return ' SQL Server Big data cluster requires kubernetes.'; + } else if (errorMessageMode === CheckPresentMessageMode.Command) { + return ' Cannot execute command.'; + } + return ''; +} + +async function invokeAsync(context: Context, command: string, stdin?: string): Promise { + if (await checkPresent(context, CheckPresentMessageMode.Command)) { + const bin = baseKubectlPath(context); + const cmd = `${bin} ${command}`; + const sr = await context.shell.exec(cmd, stdin); + if (sr && sr.code !== 0) { + checkPossibleIncompatibility(context); + } + return sr; + } else { + return { code: -1, stdout: '', stderr: '' }; + } +} + +// TODO: invalidate this when the context changes or if we know kubectl has changed (e.g. config) +let checkedCompatibility = false; // We don't want to spam the user (or CPU!) repeatedly running the version check + +async function checkPossibleIncompatibility(context: Context): Promise { + if (checkedCompatibility) { + return; + } + checkedCompatibility = true; + const compat = await compatibility.check((cmd) => asJson(context, cmd)); + if (!compatibility.isGuaranteedCompatible(compat) && compat.didCheck) { + const versionAlert = `kubectl version ${compat.clientVersion} may be incompatible with cluster Kubernetes version ${compat.serverVersion}`; + context.host.showWarningMessage(versionAlert); + } +} + + +function baseKubectlPath(context: Context): string { + let bin = getToolPath(context.host, context.shell, 'kubectl'); + if (!bin) { + bin = 'kubectl'; + } + return bin; +} + +async function asJson(context: Context, command: string): Promise> { + const shellResult = await invokeAsync(context, command); + if (!shellResult) { + return { succeeded: false, error: [`Unable to run command (${command})`] }; + } + + if (shellResult.code === 0) { + return { succeeded: true, result: JSON.parse(shellResult.stdout.trim()) as T }; + + } + return { succeeded: false, error: [ shellResult.stderr ] }; +} \ No newline at end of file diff --git a/extensions/big-data-cluster/src/kubectl/kubectlUtils.ts b/extensions/big-data-cluster/src/kubectl/kubectlUtils.ts new file mode 100644 index 0000000000..ba43a9a253 --- /dev/null +++ b/extensions/big-data-cluster/src/kubectl/kubectlUtils.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from "vscode"; +import { Kubectl } from "./kubectl"; +import { failed } from "../interfaces"; + +export interface KubectlContext { + readonly clusterName: string; + readonly contextName: string; + readonly userName: string; + readonly active: boolean; +} + +interface Kubeconfig { + readonly apiVersion: string; + readonly 'current-context': string; + readonly clusters: { + readonly name: string; + readonly cluster: { + readonly server: string; + readonly 'certificate-authority'?: string; + readonly 'certificate-authority-data'?: string; + }; + }[] | undefined; + readonly contexts: { + readonly name: string; + readonly context: { + readonly cluster: string; + readonly user: string; + readonly namespace?: string; + }; + }[] | undefined; + readonly users: { + readonly name: string; + readonly user: {}; + }[] | undefined; +} + +export interface ClusterConfig { + readonly server: string; + readonly certificateAuthority: string | undefined; +} + + + +async function getKubeconfig(kubectl: Kubectl): Promise { + const shellResult = await kubectl.asJson("config view -o json"); + if (failed(shellResult)) { + vscode.window.showErrorMessage(shellResult.error[0]); + return null; + } + return shellResult.result; +} + +export async function getCurrentClusterConfig(kubectl: Kubectl): Promise { + const kubeConfig = await getKubeconfig(kubectl); + if (!kubeConfig || !kubeConfig.clusters || !kubeConfig.contexts) { + return undefined; + } + const contextConfig = kubeConfig.contexts.find((context) => context.name === kubeConfig["current-context"])!; + const clusterConfig = kubeConfig.clusters.find((cluster) => cluster.name === contextConfig.context.cluster)!; + return { + server: clusterConfig.cluster.server, + certificateAuthority: clusterConfig.cluster["certificate-authority"] + }; +} + +export async function getContexts(kubectl: Kubectl): Promise { + const kubectlConfig = await getKubeconfig(kubectl); + if (!kubectlConfig) { + return []; + } + const currentContext = kubectlConfig["current-context"]; + const contexts = kubectlConfig.contexts || []; + return contexts.map((c) => { + return { + clusterName: c.context.cluster, + contextName: c.name, + userName: c.context.user, + active: c.name === currentContext + }; + }); +} \ No newline at end of file diff --git a/extensions/big-data-cluster/src/main.ts b/extensions/big-data-cluster/src/main.ts index c4105a8e60..4f5c74a77c 100644 --- a/extensions/big-data-cluster/src/main.ts +++ b/extensions/big-data-cluster/src/main.ts @@ -6,10 +6,22 @@ import vscode = require('vscode'); import { MainController } from './mainController'; + +import { fs } from './utility/fs'; + +import { host } from './kubectl/host'; +import { sqlserverbigdataclusterchannel } from './kubectl/kubeChannel'; +import { shell, Shell } from './utility/shell'; +import { CheckPresentMessageMode, create as kubectlCreate } from './kubectl/kubectl'; +import { installKubectl } from './installer/installer'; +import { Errorable, failed } from './interfaces'; + +const kubectl = kubectlCreate(host, fs, shell, installDependencies); export let controller: MainController; export function activate(context: vscode.ExtensionContext) { - controller = new MainController(context); + kubectl.checkPresent(CheckPresentMessageMode.Activation); + controller = new MainController(context, kubectl); controller.activate(); } @@ -19,3 +31,28 @@ export function deactivate(): void { controller.deactivate(); } } + +export async function installDependencies() { + const gotKubectl = await kubectl.checkPresent(CheckPresentMessageMode.Silent); + + + const installPromises = [ + installDependency("kubectl", gotKubectl, installKubectl), + ]; + + await Promise.all(installPromises); + + sqlserverbigdataclusterchannel.showOutput("Done"); +} + +async function installDependency(name: string, alreadyGot: boolean, installFunc: (shell: Shell) => Promise>): Promise { + if (alreadyGot) { + sqlserverbigdataclusterchannel.showOutput(`Already got ${name}...`); + } else { + sqlserverbigdataclusterchannel.showOutput(`Installing ${name}...`); + const result = await installFunc(shell); + if (failed(result)) { + sqlserverbigdataclusterchannel.showOutput(`Unable to install ${name}: ${result.error[0]}`); + } + } +} \ No newline at end of file diff --git a/extensions/big-data-cluster/src/mainController.ts b/extensions/big-data-cluster/src/mainController.ts index 00ae5964ce..1ba207ba44 100644 --- a/extensions/big-data-cluster/src/mainController.ts +++ b/extensions/big-data-cluster/src/mainController.ts @@ -6,15 +6,17 @@ import * as vscode from 'vscode'; import { CreateClusterWizard } from './wizards/create-cluster/createClusterWizard'; - +import { Kubectl } from './kubectl/kubectl'; /** * The main controller class that initializes the extension */ export class MainController { protected _context: vscode.ExtensionContext; + protected _kubectl : Kubectl; - public constructor(context: vscode.ExtensionContext) { + public constructor(context: vscode.ExtensionContext, kubectl: Kubectl) { this._context = context; + this._kubectl = kubectl; } /** @@ -22,7 +24,7 @@ export class MainController { */ public activate(): void { vscode.commands.registerCommand('mssql.cluster.create', () => { - let wizard = new CreateClusterWizard(this._context); + let wizard = new CreateClusterWizard(this._context, this._kubectl); wizard.open(); }); } diff --git a/extensions/big-data-cluster/src/utility/fs.ts b/extensions/big-data-cluster/src/utility/fs.ts new file mode 100644 index 0000000000..68326a58f2 --- /dev/null +++ b/extensions/big-data-cluster/src/utility/fs.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as sysfs from 'fs'; + +export interface FS { + existsSync(path: string): boolean; + readFile(filename: string, encoding: string, callback: (err: NodeJS.ErrnoException, data: string) => void): void; + readFileSync(filename: string, encoding: string): string; + readFileToBufferSync(filename: string): Buffer; + writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; + writeFileSync(filename: string, data: any): void; + dirSync(path: string): string[]; + unlinkAsync(path: string): Promise; + existsAsync(path: string): Promise; + openAsync(path: string, flags: string): Promise; + statSync(path: string): sysfs.Stats; +} + +export const fs: FS = { + existsSync: (path) => sysfs.existsSync(path), + readFile: (filename, encoding, callback) => sysfs.readFile(filename, encoding, callback), + readFileSync: (filename, encoding) => sysfs.readFileSync(filename, encoding), + readFileToBufferSync: (filename) => sysfs.readFileSync(filename), + writeFile: (filename, data, callback) => sysfs.writeFile(filename, data, callback), + writeFileSync: (filename, data) => sysfs.writeFileSync(filename, data), + dirSync: (path) => sysfs.readdirSync(path), + + unlinkAsync: (path) => { + return new Promise((resolve, reject) => { + sysfs.unlink(path, (error) => { + if (error) { + reject(); + return; + } + + resolve(); + }); + }); + }, + + existsAsync: (path) => { + return new Promise((resolve) => { + sysfs.exists(path, (exists) => { + resolve(exists); + }); + }); + }, + + openAsync: (path, flags) => { + return new Promise((resolve, reject) => { + sysfs.open(path, flags, (error, _fd) => { + if (error) { + reject(); + return; + } + + resolve(); + }); + }); + }, + + statSync: (path) => sysfs.statSync(path) +}; diff --git a/extensions/big-data-cluster/src/utility/shell.ts b/extensions/big-data-cluster/src/utility/shell.ts new file mode 100644 index 0000000000..6a7ca5c965 --- /dev/null +++ b/extensions/big-data-cluster/src/utility/shell.ts @@ -0,0 +1,204 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as vscode from 'vscode'; +import * as shelljs from 'shelljs'; +import * as path from 'path'; +import { getActiveKubeconfig, getToolPath } from '../config/config'; +import { host } from '../kubectl/host'; + +export enum Platform { + Windows, + MacOS, + Linux, + Unsupported, +} + +export interface ExecCallback extends shelljs.ExecCallback {} + +export interface Shell { + isWindows(): boolean; + isUnix(): boolean; + platform(): Platform; + home(): string; + combinePath(basePath: string, relativePath: string): string; + fileUri(filePath: string): vscode.Uri; + execOpts(): any; + exec(cmd: string, stdin?: string): Promise; + execCore(cmd: string, opts: any, stdin?: string): Promise; + unquotedPath(path: string): string; + which(bin: string): string | null; + cat(path: string): string; + ls(path: string): string[]; +} + +export const shell: Shell = { + isWindows : isWindows, + isUnix : isUnix, + platform : platform, + home : home, + combinePath : combinePath, + fileUri : fileUri, + execOpts : execOpts, + exec : exec, + execCore : execCore, + unquotedPath : unquotedPath, + which: which, + cat: cat, + ls: ls, +}; + +const WINDOWS: string = 'win32'; + +export interface ShellResult { + readonly code: number; + readonly stdout: string; + readonly stderr: string; +} + +export type ShellHandler = (code: number, stdout: string, stderr: string) => void; + +function isWindows(): boolean { + return (process.platform === WINDOWS); +} + +function isUnix(): boolean { + return !isWindows(); +} + +function platform(): Platform { + switch (process.platform) { + case 'win32': return Platform.Windows; + case 'darwin': return Platform.MacOS; + case 'linux': return Platform.Linux; + default: return Platform.Unsupported; + } +} + +function concatIfBoth(s1: string | undefined, s2: string | undefined): string | undefined { + return s1 && s2 ? s1.concat(s2) : undefined; +} + +function home(): string { + return process.env['HOME'] || + concatIfBoth(process.env['HOMEDRIVE'], process.env['HOMEPATH']) || + process.env['USERPROFILE'] || + ''; +} + +function combinePath(basePath: string, relativePath: string) { + let separator = '/'; + if (isWindows()) { + relativePath = relativePath.replace(/\//g, '\\'); + separator = '\\'; + } + return basePath + separator + relativePath; +} + +function isWindowsFilePath(filePath: string) { + return filePath[1] === ':' && filePath[2] === '\\'; +} + +function fileUri(filePath: string): vscode.Uri { + if (isWindowsFilePath(filePath)) { + return vscode.Uri.parse('file:///' + filePath.replace(/\\/g, '/')); + } + return vscode.Uri.parse('file://' + filePath); +} + +function execOpts(): any { + let env = process.env; + if (isWindows()) { + env = Object.assign({ }, env, { HOME: home() }); + } + env = shellEnvironment(env); + const opts = { + cwd: vscode.workspace.rootPath, + env: env, + async: true + }; + return opts; +} + +async function exec(cmd: string, stdin?: string): Promise { + try { + return await execCore(cmd, execOpts(), stdin); + } catch (ex) { + vscode.window.showErrorMessage(ex); + return undefined; + } +} + +function execCore(cmd: string, opts: any, stdin?: string): Promise { + return new Promise((resolve) => { + const proc = shelljs.exec(cmd, opts, (code, stdout, stderr) => resolve({code : code, stdout : stdout, stderr : stderr})); + if (stdin) { + proc.stdin.end(stdin); + } + }); +} + +function unquotedPath(path: string): string { + if (isWindows() && path && path.length > 1 && path.startsWith('"') && path.endsWith('"')) { + return path.substring(1, path.length - 1); + } + return path; +} + +export function shellEnvironment(baseEnvironment: any): any { + const env = Object.assign({}, baseEnvironment); + const pathVariable = pathVariableName(env); + for (const tool of ['kubectl']) { + const toolPath = getToolPath(host, shell, tool); + if (toolPath) { + const toolDirectory = path.dirname(toolPath); + const currentPath = env[pathVariable]; + env[pathVariable] = toolDirectory + (currentPath ? `${pathEntrySeparator()}${currentPath}` : ''); + } + } + + const kubeconfig = getActiveKubeconfig(); + if (kubeconfig) { + env['KUBECONFIG'] = kubeconfig; + } + + return env; +} + +function pathVariableName(env: any): string { + if (isWindows()) { + for (const v of Object.keys(env)) { + if (v.toLowerCase() === "path") { + return v; + } + } + } + return "PATH"; +} + +function pathEntrySeparator() { + return isWindows() ? ';' : ':'; +} + +function which(bin: string): string | null { + return shelljs.which(bin); +} + +function cat(path: string): string { + return shelljs.cat(path); +} + +function ls(path: string): string[] { + return shelljs.ls(path); +} + +export function shellMessage(sr: ShellResult | undefined, invocationFailureMessage: string): string { + if (!sr) { + return invocationFailureMessage; + } + return sr.code === 0 ? sr.stdout : sr.stderr; +} diff --git a/extensions/big-data-cluster/src/wizards/create-cluster/createClusterModel.ts b/extensions/big-data-cluster/src/wizards/create-cluster/createClusterModel.ts index 2528727598..c4e67303d8 100644 --- a/extensions/big-data-cluster/src/wizards/create-cluster/createClusterModel.ts +++ b/extensions/big-data-cluster/src/wizards/create-cluster/createClusterModel.ts @@ -6,6 +6,8 @@ import { IKubeConfigParser } from '../../data/kubeConfigParser'; import { ClusterInfo, TargetClusterType, ClusterPorts, ContainerRegistryInfo, TargetClusterTypeInfo, ToolInfo } from '../../interfaces'; +import { getContexts, KubectlContext } from '../../kubectl/kubectlUtils'; +import { Kubectl } from '../../kubectl/kubectl'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); @@ -14,11 +16,11 @@ export class CreateClusterModel { private _tmp_tools_installed: boolean = false; - constructor(private _kubeConfigParser: IKubeConfigParser) { + constructor(private _kubectl : Kubectl) { } - public loadClusters(configPath: string): ClusterInfo[] { - return this._kubeConfigParser.parse(configPath); + public async loadClusters(): Promise { + return await getContexts(this._kubectl); } public getDefaultPorts(): Thenable { @@ -107,7 +109,7 @@ export class CreateClusterModel { public targetClusterType: TargetClusterType; - public selectedCluster: ClusterInfo; + public selectedCluster: KubectlContext; public adminUserName: string; diff --git a/extensions/big-data-cluster/src/wizards/create-cluster/createClusterWizard.ts b/extensions/big-data-cluster/src/wizards/create-cluster/createClusterWizard.ts index d1775b6cab..189ef6a70a 100644 --- a/extensions/big-data-cluster/src/wizards/create-cluster/createClusterWizard.ts +++ b/extensions/big-data-cluster/src/wizards/create-cluster/createClusterWizard.ts @@ -9,18 +9,17 @@ import { SelectExistingClusterPage } from './pages/selectExistingClusterPage'; import { SummaryPage } from './pages/summaryPage'; import { SettingsPage } from './pages/settingsPage'; import { ClusterProfilePage } from './pages/clusterProfilePage'; -import { TestKubeConfigParser } from '../../data/kubeConfigParser'; import { ExtensionContext } from 'vscode'; import { WizardBase } from '../wizardBase'; import * as nls from 'vscode-nls'; +import { Kubectl } from '../../kubectl/kubectl'; import { SelectTargetClusterTypePage } from './pages/selectTargetClusterTypePage'; const localize = nls.loadMessageBundle(); export class CreateClusterWizard extends WizardBase { - constructor(context: ExtensionContext) { - let configParser = new TestKubeConfigParser(); - let model = new CreateClusterModel(configParser); + constructor(context: ExtensionContext, kubectl: Kubectl) { + let model = new CreateClusterModel(kubectl); super(model, context, localize('bdc-create.wizardTitle', 'Create a big data cluster')); } diff --git a/extensions/big-data-cluster/src/wizards/create-cluster/pages/selectExistingClusterPage.ts b/extensions/big-data-cluster/src/wizards/create-cluster/pages/selectExistingClusterPage.ts index 39838827d4..e601414529 100644 --- a/extensions/big-data-cluster/src/wizards/create-cluster/pages/selectExistingClusterPage.ts +++ b/extensions/big-data-cluster/src/wizards/create-cluster/pages/selectExistingClusterPage.ts @@ -10,6 +10,8 @@ import * as os from 'os'; import { WizardPageBase } from '../../wizardPageBase'; import { CreateClusterWizard } from '../createClusterWizard'; import { TargetClusterType } from '../../../interfaces'; +import { setActiveKubeconfig } from '../../../config/config'; + import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); @@ -101,7 +103,7 @@ export class SelectExistingClusterPage extends WizardPageBase= 1.0.0" + +config-chain@^1.1.11: + version "1.1.12" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" + integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== + dependencies: + ini "^1.3.4" + proto-list "~1.2.1" + +content-disposition@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" @@ -31,16 +112,293 @@ debug@^3.1.0: dependencies: ms "^2.1.1" +decompress-response@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M= + dependencies: + mimic-response "^1.0.0" + +decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" + integrity sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ== + dependencies: + file-type "^5.2.0" + is-stream "^1.1.0" + tar-stream "^1.5.2" + +decompress-tarbz2@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b" + integrity sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A== + dependencies: + decompress-tar "^4.1.0" + file-type "^6.1.0" + is-stream "^1.1.0" + seek-bzip "^1.0.5" + unbzip2-stream "^1.0.9" + +decompress-targz@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee" + integrity sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w== + dependencies: + decompress-tar "^4.1.1" + file-type "^5.2.0" + is-stream "^1.1.0" + +decompress-unzip@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69" + integrity sha1-3qrM39FK6vhVePczroIQ+bSEj2k= + dependencies: + file-type "^3.8.0" + get-stream "^2.2.0" + pify "^2.3.0" + yauzl "^2.4.2" + +decompress@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d" + integrity sha1-eu3YVCflqS2s/lVnSnxQXpbQH50= + dependencies: + decompress-tar "^4.0.0" + decompress-tarbz2 "^4.0.0" + decompress-targz "^4.0.0" + decompress-unzip "^4.0.1" + graceful-fs "^4.1.10" + make-dir "^1.0.0" + pify "^2.3.0" + strip-dirs "^2.0.0" + +download@^6.2.5: + version "6.2.5" + resolved "https://registry.yarnpkg.com/download/-/download-6.2.5.tgz#acd6a542e4cd0bb42ca70cfc98c9e43b07039714" + integrity sha512-DpO9K1sXAST8Cpzb7kmEhogJxymyVUd5qz/vCOSyvwtp2Klj2XcDt5YUuasgxka44SxF0q5RriKIwJmQHG2AuA== + dependencies: + caw "^2.0.0" + content-disposition "^0.5.2" + decompress "^4.0.0" + ext-name "^5.0.0" + file-type "5.2.0" + filenamify "^2.0.0" + get-stream "^3.0.0" + got "^7.0.0" + make-dir "^1.0.0" + p-event "^1.0.0" + pify "^3.0.0" + +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= + +end-of-stream@^1.0.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== + dependencies: + once "^1.4.0" + +escape-string-regexp@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +ext-list@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" + integrity sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA== + dependencies: + mime-db "^1.28.0" + +ext-name@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ext-name/-/ext-name-5.0.0.tgz#70781981d183ee15d13993c8822045c506c8f0a6" + integrity sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ== + dependencies: + ext-list "^2.0.0" + sort-keys-length "^1.0.0" + +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= + dependencies: + pend "~1.2.0" + +file-type@5.2.0, file-type@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" + integrity sha1-LdvqfHP/42No365J3DOMBYwritY= + +file-type@^3.8.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" + integrity sha1-JXoHg4TR24CHvESdEH1SpSZyuek= + +file-type@^6.1.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" + integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg== + +filename-reserved-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" + integrity sha1-q/c9+rc10EVECr/qLZHzieu/oik= + +filenamify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.1.0.tgz#88faf495fb1b47abfd612300002a16228c677ee9" + integrity sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA== + dependencies: + filename-reserved-regex "^2.0.0" + strip-outer "^1.0.0" + trim-repeated "^1.0.0" + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +get-proxy@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-2.1.0.tgz#349f2b4d91d44c4d4d4e9cba2ad90143fac5ef93" + integrity sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw== + dependencies: + npm-conf "^1.1.0" + +get-stream@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" + integrity sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4= + dependencies: + object-assign "^4.0.1" + pinkie-promise "^2.0.0" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= + +got@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" + integrity sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw== + dependencies: + decompress-response "^3.2.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + is-plain-obj "^1.1.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + isurl "^1.0.0-alpha5" + lowercase-keys "^1.0.0" + p-cancelable "^0.3.0" + p-timeout "^1.1.1" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + url-parse-lax "^1.0.0" + url-to-options "^1.0.1" + +graceful-fs@^4.1.10: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== + +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + integrity sha1-TK+tdrxi8C+gObL5Tpo906ORpyU= + +has-symbol-support-x@^1.4.1: + version "1.4.2" + resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" + integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== + +has-to-string-tag-x@^1.2.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" + integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw== + dependencies: + has-symbol-support-x "^1.4.1" + +ieee754@^1.1.4: + version "1.1.12" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" + integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== + +inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + is-buffer@~1.1.1: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== +is-natural-number@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" + integrity sha1-q5124dtM7VHjXeDHLr7PCfc0zeg= + +is-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" + integrity sha1-iVJojF7C/9awPsyF52ngKQMINHA= + +is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= + +is-retry-allowed@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" + integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ= + +is-stream@^1.0.0, is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isurl@^1.0.0-alpha5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" + integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w== + dependencies: + has-to-string-tag-x "^1.2.0" + is-object "^1.0.1" + lodash@^4.16.4: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== +lowercase-keys@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" + integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA== + +make-dir@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" + integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== + dependencies: + pify "^3.0.0" + md5@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" @@ -50,6 +408,16 @@ md5@^2.1.0: crypt "~0.0.1" is-buffer "~1.1.1" +mime-db@^1.28.0: + version "1.38.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" + integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== + +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -91,6 +459,138 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== +npm-conf@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/npm-conf/-/npm-conf-1.1.3.tgz#256cc47bd0e218c259c4e9550bf413bc2192aff9" + integrity sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw== + dependencies: + config-chain "^1.1.11" + pify "^3.0.0" + +object-assign@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +p-cancelable@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" + integrity sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw== + +p-event@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-1.3.0.tgz#8e6b4f4f65c72bc5b6fe28b75eda874f96a4a085" + integrity sha1-jmtPT2XHK8W2/ii3XtqHT5akoIU= + dependencies: + p-timeout "^1.1.1" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= + +p-timeout@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" + integrity sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y= + dependencies: + p-finally "^1.0.0" + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= + +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha1-ITXW36ejWMBprJsXh3YogihFD/o= + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha1-clVrgM+g1IqXToDnckjoDtT3+HA= + +prepend-http@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + +proto-list@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" + integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= + +readable-stream@^2.3.0, readable-stream@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +seek-bzip@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc" + integrity sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w= + dependencies: + commander "~2.8.1" + +sort-keys-length@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" + integrity sha1-nLb09OnkgVWmqgZx7dM2/xR5oYg= + dependencies: + sort-keys "^1.0.0" + +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0= + dependencies: + is-plain-obj "^1.0.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -98,12 +598,111 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" +strip-dirs@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" + integrity sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g== + dependencies: + is-natural-number "^4.0.1" + +strip-outer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.1.tgz#b2fd2abf6604b9d1e6013057195df836b8a9d631" + integrity sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg== + dependencies: + escape-string-regexp "^1.0.2" + +tar-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555" + integrity sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A== + dependencies: + bl "^1.0.0" + buffer-alloc "^1.2.0" + end-of-stream "^1.0.0" + fs-constants "^1.0.0" + readable-stream "^2.3.0" + to-buffer "^1.1.1" + xtend "^4.0.0" + +through@^2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +timed-out@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8= + +to-buffer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" + integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== + +trim-repeated@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" + integrity sha1-42RqLqTokTEr9+rObPsFOAvAHCE= + dependencies: + escape-string-regexp "^1.0.2" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= + dependencies: + safe-buffer "^5.0.1" + +unbzip2-stream@^1.0.9: + version "1.3.3" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz#d156d205e670d8d8c393e1c02ebd506422873f6a" + integrity sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg== + dependencies: + buffer "^5.2.1" + through "^2.3.8" + +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM= + dependencies: + prepend-http "^1.0.1" + +url-to-options@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" + integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k= + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + vscode-nls@^3.2.1: version "3.2.5" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.5.tgz#25520c1955108036dec607c85e00a522f247f1a4" integrity sha512-ITtoh3V4AkWXMmp3TB97vsMaHRgHhsSFPsUdzlueSL+dRZbSNTZeOmdQv60kjCV306ghPxhDeoNUEm3+EZMuyw== +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + xml@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= + +xtend@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= + +yauzl@^2.4.2: + version "2.10.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0"