From a7f597c9431c2ee82e6175eb744c328e8c680019 Mon Sep 17 00:00:00 2001 From: Arvind Ranasaria Date: Sun, 27 Oct 2019 17:13:12 -0700 Subject: [PATCH] dependencies messages, no curl on win32, fixes to min Version (#8039) * checking temp work to move to another branch * removing freeTds * dependencies Messages and No curl on Win32 * elipsis instead of ... * add min version check post install and pr fixes * removing unnecessary comment * removing old TODO comment * fix text messages * add github toke nto electron download (#8047) * remove hardcode of kubectl version for download --- .../resource-deployment/src/interfaces.ts | 3 +- .../src/services/platformService.ts | 6 +- .../src/services/tools/azCliTool.ts | 34 +++++++---- .../src/services/tools/azdataTool.ts | 29 +++++---- .../src/services/tools/kubeCtlTool.ts | 50 +++++++++------- .../src/services/tools/toolBase.ts | 44 +++++++++++--- .../src/services/toolsService.ts | 13 +++- .../src/ui/resourceTypePickerDialog.ts | 59 +++++++++++++------ 8 files changed, 158 insertions(+), 80 deletions(-) diff --git a/extensions/resource-deployment/src/interfaces.ts b/extensions/resource-deployment/src/interfaces.ts index 2fc32b1e98..9ea3471a21 100644 --- a/extensions/resource-deployment/src/interfaces.ts +++ b/extensions/resource-deployment/src/interfaces.ts @@ -226,7 +226,6 @@ export const enum ToolStatus { } export interface ITool { - readonly status: ToolStatus; readonly isInstalling: boolean; readonly name: string; readonly displayName: string; @@ -234,10 +233,12 @@ export interface ITool { readonly type: ToolType; readonly homePage: string; readonly displayStatus: string; + readonly dependencyMessages: string[]; readonly statusDescription: string | undefined; readonly autoInstallSupported: boolean; readonly autoInstallRequired: boolean; readonly isNotInstalled: boolean; + readonly isInstalled: boolean; readonly installationPath: string; readonly needsInstallation: boolean; readonly outputChannelName: string; diff --git a/extensions/resource-deployment/src/services/platformService.ts b/extensions/resource-deployment/src/services/platformService.ts index db0efb577c..0180265096 100644 --- a/extensions/resource-deployment/src/services/platformService.ts +++ b/extensions/resource-deployment/src/services/platformService.ts @@ -157,7 +157,7 @@ export class PlatformService implements IPlatformService { return await this.runStreamedCommand(command, this._outputChannel, options); } } catch (error) { - this._outputChannel.append(localize('platformService.RunCommand.ErroredOut', "\t>>> {0} ... errored out: {1}", command, getErrorMessage(error))); //errors are localized in our code where emitted, other errors are pass through from external components that are not easily localized + this._outputChannel.append(localize('platformService.RunCommand.ErroredOut', "\t>>> {0} … errored out: {1}", command, getErrorMessage(error))); //errors are localized in our code where emitted, other errors are pass through from external components that are not easily localized if (!(options && options.ignoreError)) { throw error; } else { @@ -232,9 +232,9 @@ export class PlatformService implements IPlatformService { // Add listeners to print stdout and stderr and exit code child.on('exit', (code: number | null, signal: string | null) => { if (code !== null) { - outputChannel.appendLine(localize('platformService.RunStreamedCommand.ExitedWithCode', " >>> {0} ... exited with code: {1}", command, code)); + outputChannel.appendLine(localize('platformService.RunStreamedCommand.ExitedWithCode', " >>> {0} … exited with code: {1}", command, code)); } else { - outputChannel.appendLine(localize('platformService.RunStreamedCommand.ExitedWithSignal', " >>> {0} ... exited with signal: {1}", command, signal)); + outputChannel.appendLine(localize('platformService.RunStreamedCommand.ExitedWithSignal', " >>> {0} … exited with signal: {1}", command, signal)); } }); child.stdout.on('data', (data: string | Buffer) => { diff --git a/extensions/resource-deployment/src/services/tools/azCliTool.ts b/extensions/resource-deployment/src/services/tools/azCliTool.ts index f7e0d950cb..a4b3125fb3 100644 --- a/extensions/resource-deployment/src/services/tools/azCliTool.ts +++ b/extensions/resource-deployment/src/services/tools/azCliTool.ts @@ -7,7 +7,7 @@ import { SemVer } from 'semver'; import * as nls from 'vscode-nls'; import { Command, OsType, ToolType } from '../../interfaces'; import { IPlatformService } from '../platformService'; -import { ToolBase } from './toolBase'; +import { ToolBase, dependencyType } from './toolBase'; const localize = nls.loadMessageBundle(); const defaultInstallationRoot = '~/.local/bin'; @@ -72,6 +72,13 @@ export class AzCliTool extends ToolBase { }; } + protected dependenciesByOsType: Map = new Map([ + [OsType.linux, []], + [OsType.win32, []], + [OsType.darwin, [dependencyType.Brew]], + [OsType.others, [dependencyType.Curl]] + ]); + protected get discoveryCommand(): Command { return { command: this.discoveryCommandString('az') @@ -81,66 +88,67 @@ export class AzCliTool extends ToolBase { const win32InstallationCommands = [ { - comment: localize('resourceDeployment.AziCli.DeletingPreviousAzureCli.msi', "deleting previously downloaded azurecli.msi if one exists ..."), + comment: localize('resourceDeployment.AziCli.DeletingPreviousAzureCli.msi', "deleting previously downloaded azurecli.msi if one exists …"), command: `IF EXIST .\\AzureCLI.msi DEL /F .\\AzureCLI.msi` }, { sudo: true, - comment: localize('resourceDeployment.AziCli.DownloadingAndInstallingAzureCli', "downloading azurecli.msi and installing azure-cli ..."), + comment: localize('resourceDeployment.AziCli.DownloadingAndInstallingAzureCli', "downloading azurecli.msi and installing azure-cli …"), command: `powershell -Command "& {(New-Object System.Net.WebClient).DownloadFile('https://aka.ms/installazurecliwindows', 'AzureCLI.msi'); Start-Process msiexec.exe -Wait -ArgumentList '/I AzureCLI.msi /passive /quiet /lvx ADS_AzureCliInstall.log'}"` }, { - comment: localize('resourceDeployment.AziCli.DisplayingInstallationLog', "displaying the installation log ..."), + comment: localize('resourceDeployment.AziCli.DisplayingInstallationLog', "displaying the installation log …"), command: `type AzureCliInstall.log | findstr /i /v /c:"cached product context" | findstr /i /v /c:"has no eligible binary patches" `, ignoreError: true } ]; const macOsInstallationCommands = [ + // try to install brew ourselves { - comment: localize('resourceDeployment.AziCli.UpdatingBrewRepository', "updating your brew repository for azure-cli installation ..."), + comment: localize('resourceDeployment.AziCli.UpdatingBrewRepository', "updating your brew repository for azure-cli installation …"), command: 'brew update' }, { - comment: localize('resourceDeployment.AziCli.InstallingAzureCli', "installing azure-cli ..."), + comment: localize('resourceDeployment.AziCli.InstallingAzureCli', "installing azure-cli …"), command: 'brew install azure-cli' } ]; const linuxInstallationCommands = [ { sudo: true, - comment: localize('resourceDeployment.AziCli.AptGetUpdate', "updating repository information before installing azure-cli ..."), + comment: localize('resourceDeployment.AziCli.AptGetUpdate', "updating repository information before installing azure-cli …"), command: 'apt-get update' }, { sudo: true, - comment: localize('resourceDeployment.AziCli.AptGetPackages', "getting packages needed for azure-cli installation ..."), + comment: localize('resourceDeployment.AziCli.AptGetPackages', "getting packages needed for azure-cli installation …"), command: 'apt-get install ca-certificates curl apt-transport-https lsb-release gnupg -y' }, { sudo: true, - comment: localize('resourceDeployment.AziCli.DownloadAndInstallingSigningKey', "downloading and installing the signing key for azure-cli ..."), + comment: localize('resourceDeployment.AziCli.DownloadAndInstallingSigningKey', "downloading and installing the signing key for azure-cli …"), command: 'curl -sL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/microsoft.asc.gpg > /dev/null' }, { sudo: true, - comment: localize('resourceDeployment.AziCli.AddingAzureCliRepositoryInformation', "adding the azure-cli repository information ..."), + comment: localize('resourceDeployment.AziCli.AddingAzureCliRepositoryInformation', "adding the azure-cli repository information …"), command: 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/azure-cli/ `lsb_release -cs` main" | tee /etc/apt/sources.list.d/azure-cli.list' }, { sudo: true, - comment: localize('resourceDeployment.AziCli.AptGetUpdateAgain', "updating repository information again for azure-cli ..."), + comment: localize('resourceDeployment.AziCli.AptGetUpdateAgain', "updating repository information again for azure-cli …"), command: 'apt-get update' }, { sudo: true, - comment: localize('resourceDeployment.AziCli.InstallingAzureCli', "installing azure-cli ..."), + comment: localize('resourceDeployment.AziCli.InstallingAzureCli', "installing azure-cli …"), command: 'apt-get install azure-cli' } ]; const defaultInstallationCommands = [ { sudo: true, - comment: localize('resourceDeployment.AziCli.ScriptedInstall', "download and invoking script to install azure-cli ..."), + comment: localize('resourceDeployment.AziCli.ScriptedInstall', "download and invoking script to install azure-cli …"), command: 'curl -sL https://aka.ms/InstallAzureCLIDeb | bash' } ]; diff --git a/extensions/resource-deployment/src/services/tools/azdataTool.ts b/extensions/resource-deployment/src/services/tools/azdataTool.ts index f2d335268d..6f3cd1b211 100644 --- a/extensions/resource-deployment/src/services/tools/azdataTool.ts +++ b/extensions/resource-deployment/src/services/tools/azdataTool.ts @@ -3,14 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { EOL } from 'os'; -import * as vscode from 'vscode'; import * as path from 'path'; import { SemVer } from 'semver'; +import * as vscode from 'vscode'; import * as nls from 'vscode-nls'; +import { azdataPipInstallArgsKey, AzdataPipInstallUriKey, DeploymentConfigurationKey } from '../../constants'; import { Command, OsType, ToolType } from '../../interfaces'; import { IPlatformService } from '../platformService'; -import { ToolBase } from './toolBase'; -import { DeploymentConfigurationKey, AzdataPipInstallUriKey, azdataPipInstallArgsKey } from '../../constants'; +import { dependencyType, ToolBase } from './toolBase'; const localize = nls.loadMessageBundle(); @@ -91,11 +91,11 @@ export class AzdataTool extends ToolBase { private get defaultInstallationCommands(): Command[] { return [ { - comment: localize('resourceDeployment.Azdata.InstallUpdatePythonRequestsPackage', "installing/updating to latest version of requests python package azdata ..."), + comment: localize('resourceDeployment.Azdata.InstallUpdatePythonRequestsPackage', "installing/updating to latest version of requests python package azdata …"), command: `pip3 install -U requests` }, { - comment: localize('resourceDeployment.Azdata.InstallingAzdata', "installing azdata ..."), + comment: localize('resourceDeployment.Azdata.InstallingAzdata', "installing azdata …"), command: `pip3 install -r ${this.azdataInstallUri} ${this.azdataInstallAdditionalArgs} --quiet --user` } ]; @@ -112,38 +112,45 @@ export class AzdataTool extends ToolBase { private get azdataInstallAdditionalArgs(): string { return vscode.workspace.getConfiguration(DeploymentConfigurationKey)[azdataPipInstallArgsKey]; } + + protected dependenciesByOsType: Map = new Map([ + [OsType.linux, [dependencyType.PythonAndPip3]], + [OsType.win32, [dependencyType.PythonAndPip3]], + [OsType.darwin, [dependencyType.PythonAndPip3]], + [OsType.others, [dependencyType.PythonAndPip3]] + ]); } /* const linuxInstallationCommands = [ { sudo: true, - comment: localize('resourceDeployment.Azdata.AptGetUpdate', "updating repository information ..."), + comment: localize('resourceDeployment.Azdata.AptGetUpdate', "updating repository information …"), command: 'apt-get update' }, { sudo: true, - comment: localize('resourceDeployment.Azdata.AptGetPackages', "getting packages needed for azdata installation ..."), + comment: localize('resourceDeployment.Azdata.AptGetPackages', "getting packages needed for azdata installation …"), command: 'apt-get install gnupg ca-certificates curl apt-transport-https lsb-release -y' }, { sudo: true, - comment: localize('resourceDeployment.Azdata.DownloadAndInstallingSigningKey', "downloading and installing the signing key for azdata ..."), + comment: localize('resourceDeployment.Azdata.DownloadAndInstallingSigningKey', "downloading and installing the signing key for azdata …"), command: 'wget -qO- https://packages.microsoft.com/keys/microsoft.asc | apt-key add -' }, { sudo: true, - comment: localize('resourceDeployment.Azdata.AddingAzureCliRepositoryInformation', "adding the azdata repository information ..."), + comment: localize('resourceDeployment.Azdata.AddingAzureCliRepositoryInformation', "adding the azdata repository information …"), command: 'add-apt-repository "$(wget -qO- https://packages.microsoft.com/config/ubuntu/16.04/mssql-server-preview.list)"' }, { sudo: true, - comment: localize('resourceDeployment.Azdata.AptGetUpdate', "updating repository information ..."), + comment: localize('resourceDeployment.Azdata.AptGetUpdate', "updating repository information …"), command: 'apt-get update' }, { sudo: true, - comment: localize('resourceDeployment.Azdata.InstallingAzdata', "installing azdata ..."), + comment: localize('resourceDeployment.Azdata.InstallingAzdata', "installing azdata …"), command: 'apt-get install -y azdata-cli' } ]; diff --git a/extensions/resource-deployment/src/services/tools/kubeCtlTool.ts b/extensions/resource-deployment/src/services/tools/kubeCtlTool.ts index 01df5c4757..caa02adabc 100644 --- a/extensions/resource-deployment/src/services/tools/kubeCtlTool.ts +++ b/extensions/resource-deployment/src/services/tools/kubeCtlTool.ts @@ -7,7 +7,7 @@ import { Command, ToolType, OsType } from '../../interfaces'; import * as nls from 'vscode-nls'; import { SemVer } from 'semver'; import { IPlatformService } from '../platformService'; -import { ToolBase } from './toolBase'; +import { ToolBase, dependencyType } from './toolBase'; const localize = nls.loadMessageBundle(); @@ -75,86 +75,92 @@ export class KubeCtlTool extends ToolBase { [OsType.darwin, macOsInstallationCommands], [OsType.others, defaultInstallationCommands] ]); + + protected dependenciesByOsType: Map = new Map([ + [OsType.linux, []], + [OsType.win32, []], + [OsType.darwin, [dependencyType.Brew]], + [OsType.others, [dependencyType.Curl]] + ]); } const macOsInstallationCommands = [ { - comment: localize('resourceDeployment.Kubectl.UpdatingBrewRepository', "updating your brew repository for kubectl installation ..."), + comment: localize('resourceDeployment.Kubectl.UpdatingBrewRepository', "updating your brew repository for kubectl installation …"), command: 'brew update' }, { - comment: localize('resourceDeployment.Kubectl.InstallingKubeCtl', "installing kubectl ..."), + comment: localize('resourceDeployment.Kubectl.InstallingKubeCtl', "installing kubectl …"), command: 'brew install kubectl' } ]; const linuxInstallationCommands = [ { sudo: true, - comment: localize('resourceDeployment.Kubectl.AptGetUpdate', "updating repository information ..."), + comment: localize('resourceDeployment.Kubectl.AptGetUpdate', "updating repository information …"), command: 'apt-get update' }, { sudo: true, - comment: localize('resourceDeployment.Kubectl.AptGetPackages', "getting packages needed for kubectl installation ..."), + comment: localize('resourceDeployment.Kubectl.AptGetPackages', "getting packages needed for kubectl installation …"), command: 'apt-get install -y apt-transport-https' }, { sudo: true, - comment: localize('resourceDeployment.Kubectl.DownloadAndInstallingSigningKey', "downloading and installing the signing key for kubectl ..."), + comment: localize('resourceDeployment.Kubectl.DownloadAndInstallingSigningKey', "downloading and installing the signing key for kubectl …"), command: 'curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -' }, { sudo: true, - comment: localize('resourceDeployment.Kubectl.AddingKubectlRepositoryInformation', "adding the kubectl repository information ..."), + comment: localize('resourceDeployment.Kubectl.AddingKubectlRepositoryInformation', "adding the kubectl repository information …"), command: 'echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" | tee -a /etc/apt/sources.list.d/kubernetes.list' }, { sudo: true, - comment: localize('resourceDeployment.Kubectl.AptGetUpdate', "updating repository information ..."), + comment: localize('resourceDeployment.Kubectl.AptGetUpdate', "updating repository information …"), command: 'apt-get update' }, { sudo: true, - comment: localize('resourceDeployment.Kubectl.InstallingKubectl', "installing kubectl ..."), + comment: localize('resourceDeployment.Kubectl.InstallingKubectl', "installing kubectl …"), command: 'apt-get install -y kubectl' } ]; -// TODO: Remove dependency on curl on Win32 and use powershell Invoke-WebRequest instead const win32InstallationCommands = [ { - comment: localize('resourceDeployment.Kubectl.DeletePreviousDownloadedKubectl.exe', "deleting previously downloaded kubectl.exe if one exists ..."), - command: `IF EXIST .\kubectl.exe DEL /F .\kubectl.exe`, + comment: localize('resourceDeployment.Kubectl.DeletePreviousDownloadedKubectl.exe', "deleting previously downloaded kubectl.exe if one exists …"), + command: 'IF EXIST .\\kubectl.exe DEL /F .\\kubectl.exe', }, { - comment: localize('resourceDeployment.Kubectl.DownloadingAndInstallingKubectl', "downloading and installing the latest kubectl.exe ..."), - command: `for /f %i in ('curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt') do curl -LO https://storage.googleapis.com/kubernetes-release/release/%i/bin/windows/amd64/kubectl.exe` + comment: localize('resourceDeployment.Kubectl.DownloadingAndInstallingKubectl', "downloading and installing the latest kubectl.exe …"), + command: `powershell -Command "& {$WebClient = New-Object System.Net.WebClient; $Version=$WebClient.DownloadString('https://storage.googleapis.com/kubernetes-release/release/stable.txt').Trim();Write-Output \\\"KubeCtl Version=$Version\\\";$Url=\\\"https://storage.googleapis.com/kubernetes-release/release/$Version/bin/windows/amd64/kubectl.exe\\\"; Write-Output \\\"Downloading file: $Url\\\"; $WebClient.DownloadFile($Url, 'kubectl.exe')}"` } ]; const defaultInstallationCommands = [ { - comment: localize('resourceDeployment.Kubectl.DeletePreviousDownloadedKubectl', "deleting previously downloaded kubectl if one exists ..."), + comment: localize('resourceDeployment.Kubectl.DeletePreviousDownloadedKubectl', "deleting previously downloaded kubectl if one exists …"), command: `[ -e ./kubectl ] && rm -f ./kubectl`, }, { - comment: localize('resourceDeployment.Kubectl.DownloadingKubectl', "downloading the latest kubectl release ..."), + comment: localize('resourceDeployment.Kubectl.DownloadingKubectl', "downloading the latest kubectl release …"), command: 'curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl' }, { - comment: localize('resourceDeployment.Kubectl.MakingExecutable', "making kubectl executable ..."), + comment: localize('resourceDeployment.Kubectl.MakingExecutable', "making kubectl executable …"), command: 'chmod +x ./kubectl', }, { sudo: true, - comment: localize('resourceDeployment.Kubectl.CleaningUpOldBackups', "cleaning up any previously backed up version in the install location if they exist ..."), - command: `[ -e /usr/local/bin/kubectl] && [ -e /usr/local/bin/kubectl_movedByADS ] && rm -f /usr/local/bin/kubectl_movedByADS` + comment: localize('resourceDeployment.Kubectl.CleaningUpOldBackups', "cleaning up any previously backed up version in the install location if they exist …"), + command: '[ -e /usr/local/bin/kubectl] && [ -e /usr/local/bin/kubectl_movedByADS ] && rm -f /usr/local/bin/kubectl_movedByADS' }, { sudo: true, - comment: localize('resourceDeployment.Kubectl.BackupCurrentBinary', "backing up any existing kubectl in the install location ..."), - command: `[ -e /usr/local/bin/kubectl ] && mv /usr/local/bin/kubectl /usr/local/bin/kubectl_movedByADS` + comment: localize('resourceDeployment.Kubectl.BackupCurrentBinary', "backing up any existing kubectl in the install location …"), + command: '[ -e /usr/local/bin/kubectl ] && mv /usr/local/bin/kubectl /usr/local/bin/kubectl_movedByADS' }, { - comment: localize('resourceDeployment.Kubectl.MoveToSystemPath', "moving kubectl into the install location in the PATH ..."), + comment: localize('resourceDeployment.Kubectl.MoveToSystemPath', "moving kubectl into the install location in the PATH …"), sudo: true, command: 'mv ./kubectl /usr/local/bin/kubectl' } diff --git a/extensions/resource-deployment/src/services/tools/toolBase.ts b/extensions/resource-deployment/src/services/tools/toolBase.ts index 154311a2d5..103161835f 100644 --- a/extensions/resource-deployment/src/services/tools/toolBase.ts +++ b/extensions/resource-deployment/src/services/tools/toolBase.ts @@ -14,7 +14,7 @@ import { IPlatformService } from '../platformService'; const localize = nls.loadMessageBundle(); const toolStatusNotInstalled: string = localize('deploymentDialog.ToolStatus.NotInstalled', "Not Installed"); const toolStatusInstalled: string = localize('deploymentDialog.ToolStatus.Installed', "Installed"); -const toolStatusInstalling: string = localize('deploymentDialog.ToolStatus.Installing', "Installing"); +const toolStatusInstalling: string = localize('deploymentDialog.ToolStatus.Installing', "Installing…"); const toolStatusError: string = localize('deploymentDialog.ToolStatus.Error', "Error"); const toolStatusFailed: string = localize('deploymentDialog.ToolStatus.Failed', "Failed"); @@ -26,6 +26,22 @@ const toolStatusLocalized: Map = new Map [ToolStatus.Failed, toolStatusFailed] ]); +export const enum dependencyType { + PythonAndPip3 = 'PythonAndPip3', + Brew = 'Brew', + Curl = 'Curl' +} + +const pythonAndPip3Localized = localize('deploymentDialog.ToolInformationalMessage.PythonAndPip3', "• azdata installation needs python3 and pip3 to be pre-installed before necessary tools can be deployed"); +const brewLocalized = localize('deploymentDialog.ToolInformationalMessage.Brew', "• brew is needed for deployment of the tools and needs to be pre-installed before necessary tools can be deployed"); +const curlLocalized = localize('deploymentDialog.ToolInformationalMessage.Curl', "• curl is needed for installation and needs to be pre-installed before necessary tools can be deployed"); + +export const messageByDependencyType: Map = new Map([ + [dependencyType.PythonAndPip3, pythonAndPip3Localized], + [dependencyType.Brew, brewLocalized], + [dependencyType.Curl, curlLocalized] +]); + export abstract class ToolBase implements ITool { constructor(private _platformService: IPlatformService) { this._osType = this._platformService.osType(); @@ -38,13 +54,22 @@ export abstract class ToolBase implements ITool { abstract homePage: string; abstract autoInstallSupported: boolean; protected abstract readonly allInstallationCommands: Map; + protected readonly dependenciesByOsType: Map = new Map(); + protected abstract getVersionFromOutput(output: string): SemVer | undefined; protected readonly _onDidUpdateData = new vscode.EventEmitter(); protected readonly uninstallCommand?: string; - protected abstract readonly versionCommand: Command; + public get dependencyMessages(): string[] { + return (this.dependenciesByOsType.get(this.osType) || []).map((msgType: dependencyType) => messageByDependencyType.get(msgType)!); + } + + protected async getInstallationPath(): Promise { + return undefined; + } + protected abstract readonly discoveryCommand: Command; protected async getSearchPaths(): Promise { @@ -63,11 +88,11 @@ export abstract class ToolBase implements ITool { return this._onDidUpdateData.event; } - get status(): ToolStatus { + protected get status(): ToolStatus { return this._status; } - set status(value: ToolStatus) { + protected set status(value: ToolStatus) { this._status = value; this._onDidUpdateData.fire(this); } @@ -84,6 +109,10 @@ export abstract class ToolBase implements ITool { return this.status === ToolStatus.NotInstalled; } + public get isInstalled(): boolean { + return this.status === ToolStatus.Installed; + } + public get isInstalling(): boolean { return this.status === ToolStatus.Installing; } @@ -154,6 +183,7 @@ export abstract class ToolBase implements ITool { } public async install(): Promise { + this._statusDescription = ''; try { this.status = ToolStatus.Installing; await this.installCore(); @@ -221,6 +251,7 @@ export abstract class ToolBase implements ITool { } private async updateVersionAndGetStatus(): Promise { + this._statusDescription = ''; const commandOutput = await this._platformService.runCommand( this.versionCommand.command, { @@ -271,10 +302,7 @@ export abstract class ToolBase implements ITool { } isSameOrNewerThan(version: string): boolean { - const currentVersion = new SemVer(this.fullVersion!); - const requiredVersion = new SemVer(version); - - return compare(currentVersion, requiredVersion) >= 0; + return this._version ? compare(this._version, new SemVer(version)) >= 0 : false; } private _storagePathEnsured: boolean = false; diff --git a/extensions/resource-deployment/src/services/toolsService.ts b/extensions/resource-deployment/src/services/toolsService.ts index 9685a01a8e..6627ce1984 100644 --- a/extensions/resource-deployment/src/services/toolsService.ts +++ b/extensions/resource-deployment/src/services/toolsService.ts @@ -14,13 +14,20 @@ export interface IToolsService { } export class ToolsService implements IToolsService { - private supportedTools: ITool[]; + private supportedTools: Map; constructor(private _platformService: IPlatformService) { - this.supportedTools = [new DockerTool(this._platformService), new AzCliTool(this._platformService), new AzdataTool(this._platformService), new KubeCtlTool(this._platformService)]; + this.supportedTools = new Map( + [ + new DockerTool(this._platformService), + new AzCliTool(this._platformService), + new AzdataTool(this._platformService), + new KubeCtlTool(this._platformService) + ].map<[string, ITool]>((tool: ITool) => [tool.name, tool]) + ); } getToolByName(toolName: string): ITool | undefined { - return this.supportedTools.find(t => t.name === toolName); + return this.supportedTools.get(toolName); } } diff --git a/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts b/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts index 34c0dded10..17dc249b70 100644 --- a/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts +++ b/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts @@ -5,7 +5,7 @@ import * as azdata from 'azdata'; import { EOL } from 'os'; import * as nls from 'vscode-nls'; -import { AgreementInfo, DeploymentProvider, ITool, ResourceType, ToolStatus } from '../interfaces'; +import { AgreementInfo, DeploymentProvider, ITool, ResourceType } from '../interfaces'; import { IResourceTypeService } from '../services/resourceTypeService'; import { IToolsService } from '../services/toolsService'; import { getErrorMessage, setEnvironmentVariablesForInstallPaths } from '../utils'; @@ -70,7 +70,7 @@ export class ResourceTypePickerDialog extends DialogBase { this._agreementContainer = view.modelBuilder.divContainer().component(); const toolColumn: azdata.TableColumn = { value: localize('deploymentDialog.toolNameColumnHeader', "Tool"), - width: 150 + width: 70 }; const descriptionColumn: azdata.TableColumn = { value: localize('deploymentDialog.toolDescriptionColumnHeader', "Description"), @@ -78,15 +78,15 @@ export class ResourceTypePickerDialog extends DialogBase { }; const installStatusColumn: azdata.TableColumn = { value: localize('deploymentDialog.toolStatusColumnHeader', "Status"), - width: 100 + width: 70 }; const versionColumn: azdata.TableColumn = { - value: localize('deploymentDialog.toolVersionColumnHeader', "Version"), - width: 100 + value: localize('deploymentDialog.toolVersionColumnHeader', "Installed Version"), + width: 90 }; const minVersionColumn: azdata.TableColumn = { - value: localize('deploymentDialog.toolMinimumVersionColumnHeader', "Minimum Version"), - width: 100 + value: localize('deploymentDialog.toolMinimumVersionColumnHeader', "Required Version"), + width: 90 }; this._toolsTable = view.modelBuilder.table().withProperties({ @@ -219,8 +219,9 @@ export class ResourceTypePickerDialog extends DialogBase { }); this._toolsLoadingComponent.loading = true; this._dialogObject.okButton.enabled = false; - - Promise.all(this._tools.map(tool => tool.loadInformation())).then(async () => { + Promise.all( + this._tools.map(tool => tool.loadInformation()) + ).then(async () => { // If the local timestamp does not match the class level timestamp, it means user has changed options, ignore the results if (this.toolRefreshTimestamp !== currentRefreshTimestamp) { return; @@ -239,10 +240,9 @@ export class ResourceTypePickerDialog extends DialogBase { if (tool.statusDescription !== undefined) { console.warn(localize('deploymentDialog.DetailToolStatusDescription', "Additional status information for tool: '{0}' [ {1} ]. {2}", tool.name, tool.homePage, tool.statusDescription)); } - } else if (tool.status === ToolStatus.Installed && toolReq.version && !tool.isSameOrNewerThan(toolReq.version)) { + } else if (tool.isInstalled && toolReq.version && !tool.isSameOrNewerThan(toolReq.version)) { minVersionCheckFailed = true; messages.push(localize('deploymentDialog.ToolDoesNotMeetVersionRequirement', "'{0}' [ {1} ] does not meet the minimum version requirement, please uninstall it and restart Azure Data Studio.", tool.displayName, tool.homePage)); - } autoInstallRequired = autoInstallRequired || tool.autoInstallRequired; return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', toolReq.version || '']; @@ -260,11 +260,22 @@ export class ResourceTypePickerDialog extends DialogBase { description: messages.join(EOL) }; } else if (autoInstallRequired) { + let infoText: string[] = [localize('deploymentDialog.InstallToolsHint', "Some required tools are not installed, you can click the \"{0}\" button to install them.", this._installToolButton.label)]; + const informationalMessagesArray = this._tools.reduce((returnArray, currentTool) => { + if (currentTool.needsInstallation) { + returnArray.push(...currentTool.dependencyMessages); + } + return returnArray; + }, /* initial Value of return array*/[]); + const informationalMessagesSet: Set = new Set(informationalMessagesArray); + if (informationalMessagesSet.size > 0) { + infoText.push(...informationalMessagesSet.values()); + } // we don't have scenarios that have mixed type of tools // either we don't support auto install: docker, or we support auto install for all required tools this._dialogObject.message = { level: azdata.window.MessageLevel.Warning, - text: localize('deploymentDialog.InstallToolsHint', "Some required tools are not installed, you can click the \"{0}\" button to install them.", this._installToolButton.label) + text: infoText.join(EOL) }; } this._toolsLoadingComponent.loading = false; @@ -302,7 +313,7 @@ export class ResourceTypePickerDialog extends DialogBase { this.resourceTypeService.startDeployment(this.getCurrentProvider()); } - public updateToolsDisplayTableData(tool: ITool) { + protected updateToolsDisplayTableData(tool: ITool) { this._toolsTable.data = this._toolsTable.data.map(rowData => { if (rowData[0] === tool.displayName) { return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', rowData[4]]; @@ -323,16 +334,26 @@ export class ResourceTypePickerDialog extends DialogBase { private async installTools(): Promise { this._installToolButton.enabled = false; - let i: number = 0; + let tool: ITool; try { - for (; i < this._tools.length; i++) { - if (this._tools[i].needsInstallation) { + const toolRequirements = this.getCurrentProvider().requiredTools; + for (let i: number = 0; i < toolRequirements.length; i++) { + const toolReq = toolRequirements[i]; + tool = this.toolsService.getToolByName(toolReq.name)!; + if (tool.needsInstallation) { // Update the informational message this._dialogObject.message = { level: azdata.window.MessageLevel.Information, - text: localize('deploymentDialog.InstallingTool', "Required tool '{0}' [ {1} ] is being installed now.", this._tools[i].displayName, this._tools[i].homePage) + text: localize('deploymentDialog.InstallingTool', "Required tool '{0}' [ {1} ] is being installed now.", tool.displayName, tool.homePage) }; await this._tools[i].install(); + if (tool.isInstalled && toolReq.version && !tool.isSameOrNewerThan(toolReq.version)) { + throw new Error( + localize('deploymentDialog.ToolDoesNotMeetVersionRequirement', "'{0}' [ {1} ] does not meet the minimum version requirement, please uninstall it and restart Azure Data Studio.", + tool.displayName, tool.homePage + ) + ); + } } } // Update the informational message @@ -342,7 +363,7 @@ export class ResourceTypePickerDialog extends DialogBase { }; this._dialogObject.okButton.enabled = true; } catch (error) { - const errorMessage = this._tools[i].statusDescription || getErrorMessage(error); + const errorMessage = tool!.statusDescription || getErrorMessage(error); if (errorMessage) { // Let the tooltip status show the errorMessage just shown so that last status is visible even after showError dialogue has been dismissed. this._dialogObject.message = { @@ -350,7 +371,7 @@ export class ResourceTypePickerDialog extends DialogBase { text: errorMessage }; } - this._tools[i].showOutputChannel(/*preserverFocus*/false); + tool!.showOutputChannel(/*preserverFocus*/false); } } }