fix how failed/errored installation workflow (#8842)

* fix how failed/errored installation workflow

* address PR comments

* renaming UiControls method
This commit is contained in:
Arvind Ranasaria
2020-01-08 15:52:28 -08:00
committed by GitHub
parent 7ecb6b4427
commit 206738db63
4 changed files with 40 additions and 45 deletions

View File

@@ -241,7 +241,6 @@ export const enum ToolStatus {
} }
export interface ITool { export interface ITool {
readonly isInstalling: boolean;
readonly name: string; readonly name: string;
readonly displayName: string; readonly displayName: string;
readonly description: string; readonly description: string;
@@ -252,15 +251,14 @@ export interface ITool {
readonly statusDescription?: string; readonly statusDescription?: string;
readonly autoInstallSupported: boolean; readonly autoInstallSupported: boolean;
readonly autoInstallNeeded: boolean; readonly autoInstallNeeded: boolean;
readonly isNotInstalled: boolean; readonly status: ToolStatus;
readonly isInstalled: boolean;
readonly installationPathOrAdditionalInformation?: string; readonly installationPathOrAdditionalInformation?: string;
readonly outputChannelName: string; readonly outputChannelName: string;
readonly fullVersion?: string; readonly fullVersion?: string;
readonly onDidUpdateData: vscode.Event<ITool>; readonly onDidUpdateData: vscode.Event<ITool>;
showOutputChannel(preserveFocus?: boolean): void; showOutputChannel(preserveFocus?: boolean): void;
loadInformation(): Promise<void>; finishInitialization(): Promise<void>;
install(): Promise<void>; install(): Promise<void>;
isSameOrNewerThan(version: string): boolean; isSameOrNewerThan(version: string): boolean;
} }

View File

@@ -117,7 +117,7 @@ export class AzdataTool extends ToolBase {
{ {
sudo: true, sudo: true,
comment: localize('resourceDeployment.Azdata.DownloadingAndInstallingAzdata', "downloading Azdata.msi and installing azdata-cli …"), 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 …"), comment: localize('resourceDeployment.Azdata.DisplayingInstallationLog', "displaying the installation log …"),

View File

@@ -84,11 +84,11 @@ export abstract class ToolBase implements ITool {
return this._onDidUpdateData.event; return this._onDidUpdateData.event;
} }
protected get status(): ToolStatus { public get status(): ToolStatus {
return this._status; return this._status;
} }
protected set status(value: ToolStatus) { protected setStatus(value: ToolStatus) {
this._status = value; this._status = value;
this._onDidUpdateData.fire(this); this._onDidUpdateData.fire(this);
} }
@@ -100,19 +100,6 @@ export abstract class ToolBase implements ITool {
public get autoInstallNeeded(): boolean { public get autoInstallNeeded(): boolean {
return this.status === ToolStatus.NotInstalled && this.autoInstallSupported; 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 { public get storagePath(): string {
return this._platformService.storagePath(); return this._platformService.storagePath();
} }
@@ -178,15 +165,15 @@ export abstract class ToolBase implements ITool {
public async install(): Promise<void> { public async install(): Promise<void> {
this._statusDescription = ''; this._statusDescription = '';
try { try {
this.status = ToolStatus.Installing; this.setStatus(ToolStatus.Installing);
await this.installCore(); await this.installCore();
this.startVersionAndStatusUpdate(); this.startVersionAndStatusUpdate();
await this._pendingVersionAndStatusUpdate; await this._pendingVersionAndStatusUpdate;
} catch (error) { } catch (error) {
const errorMessage = getErrorMessage(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._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.setStatus(ToolStatus.Error);
this._installationPathOrAdditionalInformation = errorMessage; this._installationPathOrAdditionalInformation = localize('toolBase.InstallErrorInformation', "Error installing tool. See output channel '{0}' for more details", this.outputChannelName);
throw error; 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. // but if it is ToolStatus.NotInstalled then it means that installation failed with 0 exit code.
if ((this.status as ToolStatus) === ToolStatus.NotInstalled) { 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._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) { 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.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._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); throw new Error(this._statusDescription);
} }
} }
@@ -237,14 +225,14 @@ export abstract class ToolBase implements ITool {
/** /**
* Sets the tool with discovered state and version information. * 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. * and original error encountered is re-thrown so that it gets bubbled up to the caller.
*/ */
public async loadInformation(): Promise<void> { public async finishInitialization(): Promise<void> {
try { try {
await this._pendingVersionAndStatusUpdate; await this._pendingVersionAndStatusUpdate;
} catch (error) { } catch (error) {
this.status = ToolStatus.Error; this.setStatus(ToolStatus.Error);
this._statusDescription = getErrorMessage(error); this._statusDescription = getErrorMessage(error);
this._installationPathOrAdditionalInformation = this._statusDescription; this._installationPathOrAdditionalInformation = this._statusDescription;
throw error; throw error;
@@ -280,11 +268,12 @@ export abstract class ToolBase implements ITool {
// discover and set the installationPath // discover and set the installationPath
await this.setInstallationPath(); await this.setInstallationPath();
} }
this.status = ToolStatus.Installed; this.setStatus(ToolStatus.Installed);
} }
else { 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._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);
} }
} }

View File

@@ -5,7 +5,7 @@
import * as azdata from 'azdata'; import * as azdata from 'azdata';
import { EOL } from 'os'; import { EOL } from 'os';
import * as nls from 'vscode-nls'; 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 { IResourceTypeService } from '../services/resourceTypeService';
import { IToolsService } from '../services/toolsService'; import { IToolsService } from '../services/toolsService';
import { getErrorMessage, setEnvironmentVariablesForInstallPaths } from '../utils'; import { getErrorMessage, setEnvironmentVariablesForInstallPaths } from '../utils';
@@ -214,7 +214,7 @@ export class ResourceTypePickerDialog extends DialogBase {
let toolsLoadingErrors: string[] = []; let toolsLoadingErrors: string[] = [];
Promise.all( Promise.all(
this._tools.map( 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)) .then(() => this.executeToolsTableWorkflow(currentRefreshTimestamp, toolsLoadingErrors))
@@ -230,13 +230,16 @@ export class ResourceTypePickerDialog extends DialogBase {
let minVersionCheckFailed = false; let minVersionCheckFailed = false;
const toolsToAutoInstall: ITool[] = []; const toolsToAutoInstall: ITool[] = [];
let messages: string[] = toolsLoadingErrors!; let messages: string[] = toolsLoadingErrors!;
let erroredOrFailedTool: boolean = false;
this._toolsTable.data = this.toolRequirements.map(toolRequirement => { this._toolsTable.data = this.toolRequirements.map(toolRequirement => {
const tool = this.toolsService.getToolByName(toolRequirement.name)!; const tool = this.toolsService.getToolByName(toolRequirement.name)!;
// subscribe to onUpdateData event of the tool. // subscribe to onUpdateData event of the tool.
this._toDispose.push(tool.onDidUpdateData((t: ITool) => { this._toDispose.push(tool.onDidUpdateData((t: ITool) => {
this.updateToolsDisplayTableData(t); this.updateToolsDisplayTableData(t);
})); }));
if (tool.isNotInstalled) {
erroredOrFailedTool = erroredOrFailedTool || (tool.status === ToolStatus.Error || tool.status === ToolStatus.Failed);
if (tool.status === ToolStatus.NotInstalled) {
if (tool.autoInstallSupported) { if (tool.autoInstallSupported) {
toolsToAutoInstall.push(tool); 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)); 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; 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)); 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 || '']; return [tool.displayName, tool.description, tool.displayStatus, tool.fullVersion || '', toolRequirement.version || '', tool.installationPathOrAdditionalInformation || ''];
}); });
this._installToolButton.hidden = minVersionCheckFailed || (toolsToAutoInstall.length === 0); this._installToolButton.hidden = erroredOrFailedTool || minVersionCheckFailed || (toolsToAutoInstall.length === 0);
this._dialogObject.okButton.enabled = messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0); this._dialogObject.okButton.enabled = !erroredOrFailedTool && messages.length === 0 && !minVersionCheckFailed && (toolsToAutoInstall.length === 0);
if (messages.length !== 0) { if (messages.length !== 0) {
if (messages.length > 1) { if (messages.length > 1) {
messages = messages.map(message => `${message}`); messages = messages.map(message => `${message}`);
@@ -340,15 +343,19 @@ export class ResourceTypePickerDialog extends DialogBase {
return rowData; 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; * @param enable - if true the UiControls are set to be enabled, if not they are set to be disabled.
this._optionsContainer.enabled = enabled; */
this._dialogObject.cancelButton.enabled = enabled; private setUiControlsEnabled(enable: boolean): void {
// select and install tools button are controlled separately 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<void> { private async installTools(): Promise<void> {
@@ -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) text: localize('deploymentDialog.InstallingTool', "Required tool '{0}' [ {1} ] is being installed now.", tool.displayName, tool.homePage)
}; };
await this._tools[i].install(); 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( throw new Error(
localize('deploymentDialog.ToolDoesNotMeetVersionRequirement', "'{0}' [ {1} ] does not meet the minimum version requirement, please uninstall it and restart Azure Data Studio.", localize('deploymentDialog.ToolDoesNotMeetVersionRequirement', "'{0}' [ {1} ] does not meet the minimum version requirement, please uninstall it and restart Azure Data Studio.",
tool.displayName, tool.homePage tool.displayName, tool.homePage
@@ -401,8 +408,9 @@ export class ResourceTypePickerDialog extends DialogBase {
text: errorMessage text: errorMessage
}; };
} }
tool!.showOutputChannel(/*preserverFocus*/false); tool!.showOutputChannel(/*preserveFocus*/false);
} finally {
this._installationInProgress = false;
} }
this._installationInProgress = false;
} }
} }