From 206738db636c759419510ad8a165239f7a1eb6fc Mon Sep 17 00:00:00 2001 From: Arvind Ranasaria Date: Wed, 8 Jan 2020 15:52:28 -0800 Subject: [PATCH] fix how failed/errored installation workflow (#8842) * fix how failed/errored installation workflow * address PR comments * renaming UiControls method --- .../resource-deployment/src/interfaces.ts | 6 +-- .../src/services/tools/azdataTool.ts | 2 +- .../src/services/tools/toolBase.ts | 37 ++++++----------- .../src/ui/resourceTypePickerDialog.ts | 40 +++++++++++-------- 4 files changed, 40 insertions(+), 45 deletions(-) diff --git a/extensions/resource-deployment/src/interfaces.ts b/extensions/resource-deployment/src/interfaces.ts index 9d5109d999..53f2852738 100644 --- a/extensions/resource-deployment/src/interfaces.ts +++ b/extensions/resource-deployment/src/interfaces.ts @@ -241,7 +241,6 @@ export const enum ToolStatus { } export interface ITool { - readonly isInstalling: boolean; readonly name: string; readonly displayName: string; readonly description: string; @@ -252,15 +251,14 @@ export interface ITool { readonly statusDescription?: string; readonly autoInstallSupported: boolean; readonly autoInstallNeeded: boolean; - readonly isNotInstalled: boolean; - readonly isInstalled: boolean; + readonly status: ToolStatus; readonly installationPathOrAdditionalInformation?: string; readonly outputChannelName: string; readonly fullVersion?: string; readonly onDidUpdateData: vscode.Event; showOutputChannel(preserveFocus?: boolean): void; - loadInformation(): Promise; + finishInitialization(): Promise; install(): Promise; isSameOrNewerThan(version: string): boolean; } diff --git a/extensions/resource-deployment/src/services/tools/azdataTool.ts b/extensions/resource-deployment/src/services/tools/azdataTool.ts index 833eb5266a..830c748aa3 100644 --- a/extensions/resource-deployment/src/services/tools/azdataTool.ts +++ b/extensions/resource-deployment/src/services/tools/azdataTool.ts @@ -117,7 +117,7 @@ export class AzdataTool extends ToolBase { { sudo: true, comment: localize('resourceDeployment.Azdata.DownloadingAndInstallingAzdata', "downloading Azdata.msi and installing azdata-cli …"), - command: `powershell -Command "& {(New-Object System.Net.WebClient).DownloadFile('${this.azdataInstallLocation}', 'Azdata.msi'); Start-Process msiexec.exe -Wait -ArgumentList '/I Azdata.msi /passive /quiet /lvx ADS_AzdataInstall.log'}"` + command: `powershell -NoLogo -NonInteractive -NoProfile -Command "& {try {(New-Object System.Net.WebClient).DownloadFile('${this.azdataInstallLocation}', 'Azdata.msi'); Start-Process msiexec.exe -Wait -ArgumentList '/I Azdata.msi /passive /quiet /lvx ADS_AzdataInstall.log'} catch { Write-Error $_.Exception; exit 1 }}"` }, { comment: localize('resourceDeployment.Azdata.DisplayingInstallationLog', "displaying the installation log …"), diff --git a/extensions/resource-deployment/src/services/tools/toolBase.ts b/extensions/resource-deployment/src/services/tools/toolBase.ts index 2efcedb4dd..519ddd6184 100644 --- a/extensions/resource-deployment/src/services/tools/toolBase.ts +++ b/extensions/resource-deployment/src/services/tools/toolBase.ts @@ -84,11 +84,11 @@ export abstract class ToolBase implements ITool { return this._onDidUpdateData.event; } - protected get status(): ToolStatus { + public get status(): ToolStatus { return this._status; } - protected set status(value: ToolStatus) { + protected setStatus(value: ToolStatus) { this._status = value; this._onDidUpdateData.fire(this); } @@ -100,19 +100,6 @@ export abstract class ToolBase implements ITool { public get autoInstallNeeded(): boolean { return this.status === ToolStatus.NotInstalled && this.autoInstallSupported; } - - public get isNotInstalled(): boolean { - return this.status === ToolStatus.NotInstalled; - } - - public get isInstalled(): boolean { - return this.status === ToolStatus.Installed; - } - - public get isInstalling(): boolean { - return this.status === ToolStatus.Installing; - } - public get storagePath(): string { return this._platformService.storagePath(); } @@ -178,15 +165,15 @@ export abstract class ToolBase implements ITool { public async install(): Promise { this._statusDescription = ''; try { - this.status = ToolStatus.Installing; + this.setStatus(ToolStatus.Installing); await this.installCore(); this.startVersionAndStatusUpdate(); await this._pendingVersionAndStatusUpdate; } catch (error) { const errorMessage = getErrorMessage(error); this._statusDescription = localize('toolBase.InstallError', "Error installing tool '{0}' [ {1} ].{2}Error: {3}{2}See output channel '{4}' for more details", this.displayName, this.homePage, EOL, errorMessage, this.outputChannelName); - this.status = ToolStatus.Error; - this._installationPathOrAdditionalInformation = errorMessage; + this.setStatus(ToolStatus.Error); + this._installationPathOrAdditionalInformation = localize('toolBase.InstallErrorInformation', "Error installing tool. See output channel '{0}' for more details", this.outputChannelName); throw error; } @@ -194,11 +181,12 @@ export abstract class ToolBase implements ITool { // but if it is ToolStatus.NotInstalled then it means that installation failed with 0 exit code. if ((this.status as ToolStatus) === ToolStatus.NotInstalled) { this._statusDescription = localize('toolBase.InstallFailed', "Installation commands completed but version of tool '{0}' could not be detected so our installation attempt has failed. Detection Error: {1}{2}Cleaning up previous installations would help.", this.displayName, this._statusDescription, EOL); + this._installationPathOrAdditionalInformation = localize('toolBase.InstallFailInformation', "Failed to detect version post installation. See output channel '{0}' for more details", this.outputChannelName); if (this.uninstallCommand) { this._statusDescription += localize('toolBase.ManualUninstallCommand', " A possibly way to uninstall is using this command:{0} >{1}", EOL, this.uninstallCommand); } this._statusDescription += localize('toolBase.SeeOutputChannel', "{0}See output channel '{1}' for more details", EOL, this.outputChannelName); - this.status = ToolStatus.Failed; + this.setStatus(ToolStatus.Failed); throw new Error(this._statusDescription); } } @@ -237,14 +225,14 @@ export abstract class ToolBase implements ITool { /** * Sets the tool with discovered state and version information. - * Upon error the this.status field is set to ToolStatus.Error and this.statusDescription && this.installationPathOrAdditionalInformation is set to the corresponding error message + * Upon error this.status field is set to ToolStatus.Error and this.statusDescription && this.installationPathOrAdditionalInformation is set to the corresponding error message * and original error encountered is re-thrown so that it gets bubbled up to the caller. */ - public async loadInformation(): Promise { + public async finishInitialization(): Promise { try { await this._pendingVersionAndStatusUpdate; } catch (error) { - this.status = ToolStatus.Error; + this.setStatus(ToolStatus.Error); this._statusDescription = getErrorMessage(error); this._installationPathOrAdditionalInformation = this._statusDescription; throw error; @@ -280,11 +268,12 @@ export abstract class ToolBase implements ITool { // discover and set the installationPath await this.setInstallationPath(); } - this.status = ToolStatus.Installed; + this.setStatus(ToolStatus.Installed); } else { + this._installationPathOrAdditionalInformation = localize('deployCluster.GetToolVersionErrorInformation', "Error retrieving version information. See output channel '{0}' for more details", this.outputChannelName); this._statusDescription = localize('deployCluster.GetToolVersionError', "Error retrieving version information.{0}Invalid output received, get version command output: '{1}' ", EOL, commandOutput); - this.status = ToolStatus.NotInstalled; + this.setStatus(ToolStatus.NotInstalled); } } diff --git a/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts b/extensions/resource-deployment/src/ui/resourceTypePickerDialog.ts index 9e412336ba..7e53587681 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 } from '../interfaces'; +import { AgreementInfo, DeploymentProvider, ITool, ResourceType, ToolStatus } from '../interfaces'; import { IResourceTypeService } from '../services/resourceTypeService'; import { IToolsService } from '../services/toolsService'; import { getErrorMessage, setEnvironmentVariablesForInstallPaths } from '../utils'; @@ -214,7 +214,7 @@ export class ResourceTypePickerDialog extends DialogBase { let toolsLoadingErrors: string[] = []; Promise.all( this._tools.map( - tool => tool.loadInformation().catch(() => toolsLoadingErrors.push(`${tool.displayName}:${tool.statusDescription!}`)) + tool => tool.finishInitialization().catch(() => toolsLoadingErrors.push(`${tool.displayName}:${tool.statusDescription!}`)) ) ) .then(() => this.executeToolsTableWorkflow(currentRefreshTimestamp, toolsLoadingErrors)) @@ -230,13 +230,16 @@ export class ResourceTypePickerDialog extends DialogBase { let minVersionCheckFailed = false; const toolsToAutoInstall: ITool[] = []; let messages: string[] = toolsLoadingErrors!; + let erroredOrFailedTool: boolean = false; this._toolsTable.data = this.toolRequirements.map(toolRequirement => { const tool = this.toolsService.getToolByName(toolRequirement.name)!; // subscribe to onUpdateData event of the tool. this._toDispose.push(tool.onDidUpdateData((t: ITool) => { this.updateToolsDisplayTableData(t); })); - if (tool.isNotInstalled) { + + erroredOrFailedTool = erroredOrFailedTool || (tool.status === ToolStatus.Error || tool.status === ToolStatus.Failed); + if (tool.status === ToolStatus.NotInstalled) { if (tool.autoInstallSupported) { toolsToAutoInstall.push(tool); } @@ -244,14 +247,14 @@ export class ResourceTypePickerDialog extends DialogBase { messages.push(localize('deploymentDialog.ToolInformation', "'{0}' was not discovered and automated installation is not currently supported. Install '{0}' manually or ensure it is started and discoverable. Once done please restart Azure Data Studio. See [{1}] .", tool.displayName, tool.homePage)); } } - else if (tool.isInstalled && toolRequirement.version && !tool.isSameOrNewerThan(toolRequirement.version)) { + else if (tool.status === ToolStatus.Installed && toolRequirement.version && !tool.isSameOrNewerThan(toolRequirement.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)); } return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', toolRequirement.version || '', tool.installationPathOrAdditionalInformation || '']; }); - this._installToolButton.hidden = minVersionCheckFailed || (toolsToAutoInstall.length === 0); - this._dialogObject.okButton.enabled = messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0); + this._installToolButton.hidden = erroredOrFailedTool || minVersionCheckFailed || (toolsToAutoInstall.length === 0); + this._dialogObject.okButton.enabled = !erroredOrFailedTool && messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0); if (messages.length !== 0) { if (messages.length > 1) { messages = messages.map(message => `• ${message}`); @@ -340,15 +343,19 @@ export class ResourceTypePickerDialog extends DialogBase { return rowData; } }); - this.enableUiControlsWhenNotInstalling(!tool.isInstalling); // if installing the disableContainers else enable them + this.setUiControlsEnabled(tool.status !== ToolStatus.Installing); // if installing then disable ui controls else enable them } - private enableUiControlsWhenNotInstalling(enabled: boolean): void { - this._cardGroup.enabled = enabled; - this._agreementContainer.enabled = enabled; - this._optionsContainer.enabled = enabled; - this._dialogObject.cancelButton.enabled = enabled; - // select and install tools button are controlled separately + /** + * + * @param enable - if true the UiControls are set to be enabled, if not they are set to be disabled. + */ + private setUiControlsEnabled(enable: boolean): void { + this._cardGroup.enabled = enable; + this._agreementContainer.enabled = enable; + this._optionsContainer.enabled = enable; + this._dialogObject.cancelButton.enabled = enable; + // select and install tools buttons are controlled separately } private async installTools(): Promise { @@ -368,7 +375,7 @@ export class ResourceTypePickerDialog extends DialogBase { 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)) { + if (tool.status === ToolStatus.Installed && 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 @@ -401,8 +408,9 @@ export class ResourceTypePickerDialog extends DialogBase { text: errorMessage }; } - tool!.showOutputChannel(/*preserverFocus*/false); + tool!.showOutputChannel(/*preserveFocus*/false); + } finally { + this._installationInProgress = false; } - this._installationInProgress = false; } }