mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Feat/tool install master merge back to master (#7819)
* add install tools button (#7454) * add install tools button * address comments * remove description for install tools hint message * First working version of AutoDeployment of tools (#7647) First working version of AutoDeployment of tools. This pull request adds feature to install the tools needed for doing BDC/TINA deployments. This has been tested so far only on win32 and testing on other platforms is in progress. * removing TODO and redundant code * Not localizing azuredatastudio product name * convert methods returning Promises to async-await * changing from null to undefined * Localize all the command labels * using existing sudo-prompt typings * progres/error status in ModalDialogue && PR fixes * review feedback to change warning to information * revert settings.json changes * fix resource-Deployment Extension Unit Test * ensuring platform service's working directory * incorporate review feedback * review feedback * addressing PR feedback * PR fixes * PR Feedback * remove debug logs * disable UI deployment containers when installing * addding data type to stdout/stderr messaging * remove commented code * revert accidental change * addressing review feedback * fix failed install with zero exit code * fixing bug due to typo * fixes for linux * Misc fixes during mac testing * PR fixes
This commit is contained in:
@@ -66,9 +66,7 @@ export class AzdataService implements IAzdataService {
|
||||
}
|
||||
|
||||
private async ensureWorkingDirectoryExists(): Promise<void> {
|
||||
if (! await this.platformService.fileExists(this.platformService.storagePath())) {
|
||||
await this.platformService.makeDirectory(this.platformService.storagePath());
|
||||
}
|
||||
await this.platformService.ensureDirectoryExists(this.platformService.storagePath());
|
||||
}
|
||||
|
||||
private async getJsonObjectFromFile(path: string): Promise<any> {
|
||||
|
||||
@@ -2,37 +2,61 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as cp from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as cp from 'promisify-child-process';
|
||||
import * as sudo from 'sudo-prompt';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { OsType } from '../interfaces';
|
||||
import { getErrorMessage } from '../utils';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const extensionOutputChannel = localize('resourceDeployment.outputChannel', "Deployments");
|
||||
const sudoPromptTitle = 'AzureDataStudio';
|
||||
/**
|
||||
* Abstract of platform dependencies
|
||||
*/
|
||||
export interface IPlatformService {
|
||||
osType(): OsType;
|
||||
platform(): string;
|
||||
storagePath(): string;
|
||||
copyFile(source: string, target: string): Promise<void>;
|
||||
fileExists(file: string): Promise<boolean>;
|
||||
openFile(filePath: string): void;
|
||||
showErrorMessage(message: string): void;
|
||||
showErrorMessage(error: Error | string): void;
|
||||
logToOutputChannel(data: string | Buffer, header?: string): void;
|
||||
outputChannelName(): string;
|
||||
showOutputChannel(preserveFocus?: boolean): void;
|
||||
isNotebookNameUsed(title: string): boolean;
|
||||
makeDirectory(path: string): Promise<void>;
|
||||
ensureDirectoryExists(directory: string): Promise<void>;
|
||||
readTextFile(filePath: string): Promise<string>;
|
||||
runCommand(command: string, options?: CommandOptions): Promise<string>;
|
||||
saveTextFile(content: string, path: string): Promise<void>;
|
||||
deleteFile(path: string, ignoreError?: boolean): Promise<void>;
|
||||
}
|
||||
|
||||
interface CommandOutput {
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
}
|
||||
|
||||
export interface CommandOptions {
|
||||
workingDirectory?: string;
|
||||
additionalEnvironmentVariables?: NodeJS.ProcessEnv;
|
||||
sudo?: boolean;
|
||||
commandTitle?: string;
|
||||
ignoreError?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A class that provides various services to interact with the platform on which the code runs
|
||||
*/
|
||||
export class PlatformService implements IPlatformService {
|
||||
|
||||
private _outputChannel: vscode.OutputChannel = vscode.window.createOutputChannel(extensionOutputChannel);
|
||||
|
||||
constructor(private _storagePath: string = '') {
|
||||
}
|
||||
|
||||
@@ -44,57 +68,185 @@ export class PlatformService implements IPlatformService {
|
||||
return process.platform;
|
||||
}
|
||||
|
||||
copyFile(source: string, target: string): Promise<void> {
|
||||
return fs.promises.copyFile(source, target);
|
||||
outputChannelName(): string {
|
||||
return this._outputChannel.name;
|
||||
}
|
||||
|
||||
fileExists(file: string): Promise<boolean> {
|
||||
return fs.promises.access(file).then(() => {
|
||||
showOutputChannel(preserveFocus?: boolean): void {
|
||||
this._outputChannel.show(preserveFocus);
|
||||
}
|
||||
|
||||
osType(platform: string = this.platform()): OsType {
|
||||
if (Object.values(OsType).includes(<OsType>platform)) {
|
||||
return <OsType>platform;
|
||||
} else {
|
||||
return OsType.others;
|
||||
}
|
||||
}
|
||||
|
||||
async copyFile(source: string, target: string): Promise<void> {
|
||||
return await fs.promises.copyFile(source, target);
|
||||
}
|
||||
|
||||
async fileExists(file: string): Promise<boolean> {
|
||||
try {
|
||||
await fs.promises.access(file);
|
||||
return true;
|
||||
}).catch(error => {
|
||||
} catch (error) {
|
||||
if (error && error.code === 'ENOENT') {
|
||||
return false;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
openFile(filePath: string): void {
|
||||
vscode.commands.executeCommand('vscode.open', vscode.Uri.file(filePath));
|
||||
}
|
||||
|
||||
showErrorMessage(message: string): void {
|
||||
vscode.window.showErrorMessage(message);
|
||||
showErrorMessage(error: Error | string): void {
|
||||
vscode.window.showErrorMessage(getErrorMessage(error));
|
||||
}
|
||||
|
||||
isNotebookNameUsed(title: string): boolean {
|
||||
return (azdata.nb.notebookDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1);
|
||||
}
|
||||
|
||||
makeDirectory(path: string): Promise<void> {
|
||||
return fs.promises.mkdir(path);
|
||||
async makeDirectory(path: string): Promise<void> {
|
||||
await fs.promises.mkdir(path);
|
||||
}
|
||||
|
||||
readTextFile(filePath: string): Promise<string> {
|
||||
return fs.promises.readFile(filePath, 'utf8');
|
||||
/**
|
||||
*This function ensures that the given {@link directory} does not exist it creates it. It creates only the most leaf folder so if any ancestor folders are missing then this command throws an error.
|
||||
* @param directory - the path to ensure
|
||||
*/
|
||||
async ensureDirectoryExists(directory: string): Promise<void> {
|
||||
if (!await this.fileExists(directory)) {
|
||||
await this.makeDirectory(directory);
|
||||
}
|
||||
}
|
||||
|
||||
runCommand(command: string, options?: CommandOptions): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const env = Object.assign({}, process.env, options && options.additionalEnvironmentVariables);
|
||||
cp.exec(command, {
|
||||
cwd: options && options.workingDirectory,
|
||||
env: env
|
||||
}, (error, stdout, stderror) => {
|
||||
async readTextFile(filePath: string): Promise<string> {
|
||||
return await fs.promises.readFile(filePath, 'utf8');
|
||||
}
|
||||
|
||||
public logToOutputChannel(data: string | Buffer, header?: string): void {
|
||||
//input data is localized by caller
|
||||
data.toString().split(/\r?\n/)
|
||||
.forEach(line => {
|
||||
this._outputChannel.appendLine(header ? header + line : line);
|
||||
});
|
||||
}
|
||||
|
||||
private outputDataChunk(data: string | Buffer, outputChannel: vscode.OutputChannel, header: string): void {
|
||||
data.toString().split(/\r?\n/)
|
||||
.forEach(line => {
|
||||
outputChannel.appendLine(header + line);
|
||||
});
|
||||
}
|
||||
|
||||
async runCommand(command: string, options?: CommandOptions): Promise<string> {
|
||||
if (options && options.commandTitle !== undefined && options.commandTitle !== null) {
|
||||
this._outputChannel.appendLine(`\t[ ${options.commandTitle} ]`); // commandTitle inputs are localized by caller
|
||||
}
|
||||
|
||||
try {
|
||||
if (options && options.sudo) {
|
||||
return await this.runSudoCommand(command, this._outputChannel, options);
|
||||
} else {
|
||||
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
|
||||
if (!(options && options.ignoreError)) {
|
||||
throw error;
|
||||
} else {
|
||||
this._outputChannel.append(localize('platformService.RunCommand.IgnoringError', "\t>>> Ignoring error in execution and continuing tool deployment"));
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sudoExec(command: string, options: sudo.SudoOptions): Promise<CommandOutput> {
|
||||
return new Promise<CommandOutput>((resolve, reject) => {
|
||||
sudo.exec(command, options, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(stdout);
|
||||
resolve({ stdout, stderr });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async runSudoCommand(command: string, outputChannel: vscode.OutputChannel, options?: CommandOptions): Promise<string> {
|
||||
outputChannel.appendLine(` sudo> ${command}`);
|
||||
|
||||
if (options && options.workingDirectory) {
|
||||
process.chdir(options.workingDirectory);
|
||||
}
|
||||
|
||||
// Workaround for https://github.com/jorangreef/sudo-prompt/issues/111
|
||||
// DevNote: The environment variable being excluded from getting passed to sudo will never exist on a 'unixy' box. So this affects windows only.
|
||||
// On my testing on windows machine for our usage the environment variables being excluded were not important for the process execution being used here.
|
||||
// If one is trying to use this code elsewhere, one should test on windows thoroughly unless the above issue is fixed.
|
||||
const origEnv: NodeJS.ProcessEnv = Object.assign({}, process.env, options && options.additionalEnvironmentVariables);
|
||||
const env: NodeJS.ProcessEnv = {};
|
||||
|
||||
Object.keys(origEnv).filter(key => /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)).forEach((key) => {
|
||||
env[key] = origEnv[key];
|
||||
});
|
||||
// Workaround for https://github.com/jorangreef/sudo-prompt/issues/111 done
|
||||
|
||||
const sudoOptions = {
|
||||
name: sudoPromptTitle,
|
||||
env: env
|
||||
};
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await this.sudoExec(command, sudoOptions);
|
||||
this.outputDataChunk(stdout, outputChannel, localize('platformService.RunCommand.stdout', " stdout: "));
|
||||
this.outputDataChunk(stderr, outputChannel, localize('platformService.RunCommand.stderr', " stderr: "));
|
||||
return stdout;
|
||||
} catch (error) {
|
||||
this.outputDataChunk(error, outputChannel, localize('platformService.RunCommand.stderr', " stderr: "));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async runStreamedCommand(command: string, outputChannel: vscode.OutputChannel, options?: CommandOptions): Promise<string> {
|
||||
const stdoutData: string[] = [];
|
||||
outputChannel.appendLine(` > ${command}`);
|
||||
|
||||
const spawnOptions = {
|
||||
cwd: options && options.workingDirectory,
|
||||
env: Object.assign({}, process.env, options && options.additionalEnvironmentVariables),
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 10 * 1024 * 1024, // 10 Mb of output can be captured.
|
||||
shell: true,
|
||||
detached: false,
|
||||
windowsHide: true
|
||||
};
|
||||
const child = cp.spawn(command, [], spawnOptions);
|
||||
|
||||
// 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));
|
||||
} else {
|
||||
outputChannel.appendLine(localize('platformService.RunStreamedCommand.ExitedWithSignal', " >>> ${0} ... exited with signal: ${1}", command, signal));
|
||||
}
|
||||
});
|
||||
child.stdout.on('data', (data: string | Buffer) => {
|
||||
stdoutData.push(data.toString());
|
||||
this.outputDataChunk(data, outputChannel, localize('platformService.RunCommand.stdout', " stdout: "));
|
||||
});
|
||||
child.stderr.on('data', (data: string | Buffer) => { this.outputDataChunk(data, outputChannel, localize('platformService.RunCommand.stderr', " stderr: ")); });
|
||||
|
||||
await child;
|
||||
return stdoutData.join('');
|
||||
}
|
||||
|
||||
saveTextFile(content: string, path: string): Promise<void> {
|
||||
return fs.promises.writeFile(path, content, 'utf8');
|
||||
}
|
||||
@@ -108,7 +260,7 @@ export class PlatformService implements IPlatformService {
|
||||
}
|
||||
catch (error) {
|
||||
if (ignoreError) {
|
||||
console.error('Error occured deleting file: ', getErrorMessage(error));
|
||||
console.error('Error occurred deleting file: ', getErrorMessage(error));
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -2,14 +2,16 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ToolType } from '../../interfaces';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { SemVer } from 'semver';
|
||||
import { IPlatformService } from '../platformService';
|
||||
import { EOL } from 'os';
|
||||
import { SemVer } from 'semver';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Command, OsType, ToolType } from '../../interfaces';
|
||||
import { IPlatformService } from '../platformService';
|
||||
import { ToolBase } from './toolBase';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const defaultInstallationRoot = '~/.local/bin';
|
||||
const win32InstallationRoot = `${process.env['ProgramFiles(x86)']}\\Microsoft SDKs\\Azure\\CLI2\\wbin`;
|
||||
|
||||
export class AzCliTool extends ToolBase {
|
||||
constructor(platformService: IPlatformService) {
|
||||
@@ -17,11 +19,11 @@ export class AzCliTool extends ToolBase {
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return 'azcli';
|
||||
return 'azure-cli';
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return localize('resourceDeployment.AzCLIDescription', 'A command-line tool for managing Azure resources');
|
||||
return localize('resourceDeployment.AzCLIDescription', "A command-line tool for managing Azure resources");
|
||||
}
|
||||
|
||||
get type(): ToolType {
|
||||
@@ -29,13 +31,33 @@ export class AzCliTool extends ToolBase {
|
||||
}
|
||||
|
||||
get displayName(): string {
|
||||
return localize('resourceDeployment.AzCLIDisplayName', 'Azure CLI');
|
||||
return localize('resourceDeployment.AzCLIDisplayName', "Azure CLI");
|
||||
}
|
||||
|
||||
get homePage(): string {
|
||||
return 'https://docs.microsoft.com/cli/azure/install-azure-cli';
|
||||
}
|
||||
|
||||
get autoInstallSupported(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async getInstallationPath(): Promise<string | undefined> {
|
||||
switch (this.osType) {
|
||||
case OsType.win32:
|
||||
return win32InstallationRoot;
|
||||
default:
|
||||
return defaultInstallationRoot;
|
||||
}
|
||||
}
|
||||
|
||||
readonly allInstallationCommands: Map<OsType, Command[]> = new Map<OsType, Command[]>([
|
||||
[OsType.linux, linuxInstallationCommands],
|
||||
[OsType.win32, win32InstallationCommands],
|
||||
[OsType.darwin, macOsInstallationCommands],
|
||||
[OsType.others, defaultInstallationCommands]
|
||||
]);
|
||||
|
||||
protected getVersionFromOutput(output: string): SemVer | undefined {
|
||||
if (output && output.includes('azure-cli')) {
|
||||
return new SemVer(output.split(EOL)[0].replace('azure-cli', '').replace(/ /g, '').replace('*', ''));
|
||||
@@ -43,7 +65,75 @@ export class AzCliTool extends ToolBase {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
protected get versionCommand(): string {
|
||||
return 'az --version';
|
||||
protected get versionCommand(): Command {
|
||||
return {
|
||||
command: 'az --version'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const win32InstallationCommands = [
|
||||
{
|
||||
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 ..."),
|
||||
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 ..."),
|
||||
command: `type AzureCliInstall.log | findstr /i /v /c:"cached product context" | findstr /i /v /c:"has no eligible binary patches" `,
|
||||
ignoreError: true
|
||||
}
|
||||
];
|
||||
const macOsInstallationCommands = [
|
||||
{
|
||||
comment: localize('resourceDeployment.AziCli.UpdatingBrewRepository', "updating your brew repository for azure-cli installation ..."),
|
||||
command: 'brew update'
|
||||
},
|
||||
{
|
||||
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 ..."),
|
||||
command: 'apt-get update'
|
||||
},
|
||||
{
|
||||
sudo: true,
|
||||
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 ..."),
|
||||
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 ..."),
|
||||
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 ..."),
|
||||
command: 'apt-get update'
|
||||
},
|
||||
{
|
||||
sudo: true,
|
||||
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 ..."),
|
||||
command: 'curl -sL https://aka.ms/InstallAzureCLIDeb | bash'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -2,15 +2,16 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ToolType } from '../../interfaces';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { SemVer } from 'semver';
|
||||
import { EOL } from 'os';
|
||||
import * as path from 'path';
|
||||
import { SemVer } from 'semver';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Command, OsType, ToolType } from '../../interfaces';
|
||||
import { IPlatformService } from '../platformService';
|
||||
import { ToolBase } from './toolBase';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const installationRoot = '~/.local/bin';
|
||||
|
||||
export class AzdataTool extends ToolBase {
|
||||
constructor(platformService: IPlatformService) {
|
||||
@@ -37,8 +38,10 @@ export class AzdataTool extends ToolBase {
|
||||
return 'https://docs.microsoft.com/sql/big-data-cluster/deploy-install-azdata';
|
||||
}
|
||||
|
||||
protected get versionCommand(): string {
|
||||
return 'azdata -v';
|
||||
protected get versionCommand(): Command {
|
||||
return {
|
||||
command: 'azdata -v'
|
||||
};
|
||||
}
|
||||
|
||||
protected getVersionFromOutput(output: string): SemVer | undefined {
|
||||
@@ -48,4 +51,79 @@ export class AzdataTool extends ToolBase {
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
get autoInstallSupported(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async getInstallationPath(): Promise<string | undefined> {
|
||||
switch (this.osType) {
|
||||
case OsType.linux:
|
||||
return installationRoot;
|
||||
default:
|
||||
const azdataCliInstallLocation = await this.getPip3InstallLocation('azdata-cli');
|
||||
return azdataCliInstallLocation && path.join(azdataCliInstallLocation, '..', 'Scripts');
|
||||
}
|
||||
}
|
||||
|
||||
readonly allInstallationCommands: Map<OsType, Command[]> = new Map<OsType, Command[]>([
|
||||
[OsType.linux, linuxInstallationCommands],
|
||||
[OsType.win32, defaultInstallationCommands],
|
||||
[OsType.darwin, defaultInstallationCommands],
|
||||
[OsType.others, defaultInstallationCommands]
|
||||
]);
|
||||
|
||||
protected get uninstallCommand(): string | undefined {
|
||||
if (this.osType !== OsType.linux) {
|
||||
return defaultUninstallCommand;
|
||||
} else {
|
||||
return super.uninstallCommand;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const linuxInstallationCommands = [
|
||||
{
|
||||
sudo: true,
|
||||
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 ..."),
|
||||
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 ..."),
|
||||
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 ..."),
|
||||
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 ..."),
|
||||
command: 'apt-get update'
|
||||
},
|
||||
{
|
||||
sudo: true,
|
||||
comment: localize('resourceDeployment.Azdata.InstallingAzdata', "installing azdata ..."),
|
||||
command: 'apt-get install -y azdata-cli'
|
||||
}
|
||||
];
|
||||
|
||||
const defaultInstallationCommands = [
|
||||
{
|
||||
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 ..."),
|
||||
command: `pip3 install -r https://aka.ms/azdata --quiet --user`
|
||||
}
|
||||
];
|
||||
|
||||
const defaultUninstallCommand = `pip3 uninstall -r https://aka.ms/azdata -y `;
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ToolType } from '../../interfaces';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { SemVer } from 'semver';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Command, ToolType, OsType } from '../../interfaces';
|
||||
import { IPlatformService } from '../platformService';
|
||||
import { ToolBase } from './toolBase';
|
||||
|
||||
@@ -21,7 +20,7 @@ export class DockerTool extends ToolBase {
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return localize('resourceDeployment.DockerDescription', 'Provides the ability to package and run an application in isolated containers');
|
||||
return localize('resourceDeployment.DockerDescription', "Provides the ability to package and run an application in isolated containers");
|
||||
}
|
||||
|
||||
get type(): ToolType {
|
||||
@@ -29,7 +28,7 @@ export class DockerTool extends ToolBase {
|
||||
}
|
||||
|
||||
get displayName(): string {
|
||||
return localize('resourceDeployment.DockerDisplayName', 'docker');
|
||||
return localize('resourceDeployment.DockerDisplayName', "docker");
|
||||
}
|
||||
|
||||
get homePage(): string {
|
||||
@@ -43,7 +42,15 @@ export class DockerTool extends ToolBase {
|
||||
}
|
||||
return version;
|
||||
}
|
||||
protected get versionCommand(): string {
|
||||
return 'docker version --format "{{json .}}"';
|
||||
protected get versionCommand(): Command {
|
||||
return { command: 'docker version --format "{{json .}}"' };
|
||||
}
|
||||
|
||||
get autoInstallSupported(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
get allInstallationCommands(): Map<OsType, Command[]> {
|
||||
throw Error('Installation of DockerTool is not supported');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ToolType } from '../../interfaces';
|
||||
import { Command, ToolType, OsType } from '../../interfaces';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { SemVer } from 'semver';
|
||||
import { IPlatformService } from '../platformService';
|
||||
@@ -21,7 +21,7 @@ export class KubeCtlTool extends ToolBase {
|
||||
}
|
||||
|
||||
get description(): string {
|
||||
return localize('resourceDeployment.KubeCtlDescription', 'A command-line tool allows you to run commands against Kubernetes clusters');
|
||||
return localize('resourceDeployment.KubeCtlDescription', "A command-line tool allows you to run commands against Kubernetes clusters");
|
||||
}
|
||||
|
||||
get type(): ToolType {
|
||||
@@ -29,7 +29,7 @@ export class KubeCtlTool extends ToolBase {
|
||||
}
|
||||
|
||||
get displayName(): string {
|
||||
return localize('resourceDeployment.KubeCtlDisplayName', 'kubectl');
|
||||
return localize('resourceDeployment.KubeCtlDisplayName', "kubectl");
|
||||
}
|
||||
|
||||
get homePage(): string {
|
||||
@@ -45,7 +45,101 @@ export class KubeCtlTool extends ToolBase {
|
||||
return version;
|
||||
}
|
||||
|
||||
protected get versionCommand(): string {
|
||||
return 'kubectl version -o json --client';
|
||||
protected get versionCommand(): Command {
|
||||
return { command: 'kubectl version -o json --client' };
|
||||
}
|
||||
|
||||
get autoInstallSupported(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
readonly allInstallationCommands: Map<OsType, Command[]> = new Map<OsType, Command[]>([
|
||||
[OsType.linux, linuxInstallationCommands],
|
||||
[OsType.win32, win32InstallationCommands],
|
||||
[OsType.darwin, macOsInstallationCommands],
|
||||
[OsType.others, defaultInstallationCommands]
|
||||
]);
|
||||
}
|
||||
|
||||
const macOsInstallationCommands = [
|
||||
{
|
||||
comment: localize('resourceDeployment.Kubectl.UpdatingBrewRepository', "updating your brew repository for kubectl installation ..."),
|
||||
command: 'brew update'
|
||||
},
|
||||
{
|
||||
comment: localize('resourceDeployment.Kubectl.InstallingKubeCtl', "installing kubectl ..."),
|
||||
command: 'brew install kubectl'
|
||||
}
|
||||
];
|
||||
const linuxInstallationCommands = [
|
||||
{
|
||||
sudo: true,
|
||||
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 ..."),
|
||||
command: 'apt-get install -y apt-transport-https'
|
||||
},
|
||||
{
|
||||
sudo: true,
|
||||
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 ..."),
|
||||
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 ..."),
|
||||
command: 'apt-get update'
|
||||
},
|
||||
{
|
||||
sudo: true,
|
||||
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.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`
|
||||
}
|
||||
];
|
||||
const defaultInstallationCommands = [
|
||||
{
|
||||
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 ..."),
|
||||
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 ..."),
|
||||
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`
|
||||
},
|
||||
{
|
||||
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.MoveToSystemPath', "moving kubectl into the install location in the PATH ..."),
|
||||
sudo: true,
|
||||
command: 'mv ./kubectl /usr/local/bin/kubectl'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -2,62 +2,246 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ToolType, ITool } from '../../interfaces';
|
||||
import { SemVer } from 'semver';
|
||||
import { IPlatformService } from '../platformService';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { EOL } from 'os';
|
||||
import { delimiter } from 'path';
|
||||
import { SemVer } from 'semver';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Command, ITool, OsType, ToolStatus, ToolType } from '../../interfaces';
|
||||
import { getErrorMessage } from '../../utils';
|
||||
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.NotInstalling', "Installing ...");
|
||||
const toolStatusError: string = localize('deploymentDialog.ToolStatus.Error', "Error");
|
||||
const toolStatusFailed: string = localize('deploymentDialog.ToolStatus.Failed', "Failed");
|
||||
|
||||
const toolStatusLocalized: Map<ToolStatus, string> = new Map<ToolStatus, string>([
|
||||
[ToolStatus.Error, toolStatusError],
|
||||
[ToolStatus.Installed, toolStatusInstalled],
|
||||
[ToolStatus.Installing, toolStatusInstalling],
|
||||
[ToolStatus.NotInstalled, toolStatusNotInstalled],
|
||||
[ToolStatus.Failed, toolStatusFailed]
|
||||
]);
|
||||
|
||||
export abstract class ToolBase implements ITool {
|
||||
constructor(private _platformService: IPlatformService) { }
|
||||
constructor(private _platformService: IPlatformService) {
|
||||
this._osType = this._platformService.osType();
|
||||
}
|
||||
|
||||
abstract name: string;
|
||||
abstract displayName: string;
|
||||
abstract description: string;
|
||||
abstract type: ToolType;
|
||||
abstract homePage: string;
|
||||
protected abstract getVersionFromOutput(output: string): SemVer | undefined;
|
||||
protected abstract readonly versionCommand: string;
|
||||
abstract autoInstallSupported: boolean;
|
||||
abstract readonly allInstallationCommands: Map<OsType, Command[]>;
|
||||
|
||||
public get version(): SemVer | undefined {
|
||||
protected abstract getVersionFromOutput(output: string): SemVer | undefined;
|
||||
protected readonly _onDidUpdateData = new vscode.EventEmitter<ITool>();
|
||||
protected readonly uninstallCommand?: string;
|
||||
|
||||
|
||||
protected abstract readonly versionCommand: Command;
|
||||
|
||||
protected async getInstallationPath(): Promise<string | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
protected get installationSearchPaths(): (string | undefined)[] {
|
||||
return [this.storagePath];
|
||||
}
|
||||
|
||||
protected get downloadPath(): string {
|
||||
return this.storagePath;
|
||||
}
|
||||
|
||||
protected logToOutputChannel(data: string | Buffer, header?: string): void {
|
||||
this._platformService.logToOutputChannel(data, header); // data and header are localized by caller
|
||||
}
|
||||
|
||||
public get onDidUpdateData(): vscode.Event<ITool> {
|
||||
return this._onDidUpdateData.event;
|
||||
}
|
||||
|
||||
protected get status(): ToolStatus {
|
||||
return this._status;
|
||||
}
|
||||
|
||||
protected set status(value: ToolStatus) {
|
||||
this._status = value;
|
||||
this._onDidUpdateData.fire(this);
|
||||
}
|
||||
|
||||
public get displayStatus(): string {
|
||||
return <string>toolStatusLocalized.get(this._status);
|
||||
}
|
||||
|
||||
public get autoInstallRequired(): boolean {
|
||||
return this.status !== ToolStatus.Installed && this.autoInstallSupported;
|
||||
}
|
||||
|
||||
public get isNotInstalled(): boolean {
|
||||
return this.status === ToolStatus.NotInstalled;
|
||||
}
|
||||
|
||||
public get isInstalling(): boolean {
|
||||
return this.status === ToolStatus.Installing;
|
||||
}
|
||||
|
||||
public get needsInstallation(): boolean {
|
||||
return this.status !== ToolStatus.Installed;
|
||||
}
|
||||
|
||||
public get storagePath(): string {
|
||||
const storagePath = this._platformService.storagePath();
|
||||
if (!this._storagePathEnsured) {
|
||||
this._platformService.ensureDirectoryExists(storagePath);
|
||||
this._storagePathEnsured = true;
|
||||
}
|
||||
return storagePath;
|
||||
}
|
||||
|
||||
public get osType(): OsType {
|
||||
return this._osType;
|
||||
}
|
||||
|
||||
protected get version(): SemVer | undefined {
|
||||
return this._version;
|
||||
}
|
||||
|
||||
public get isInstalled(): boolean {
|
||||
return this._isInstalled;
|
||||
protected set version(value: SemVer | undefined) {
|
||||
this._version = value;
|
||||
this._onDidUpdateData.fire(this);
|
||||
}
|
||||
|
||||
public get fullVersion(): string | undefined {
|
||||
return this._version && this._version.version;
|
||||
|
||||
}
|
||||
|
||||
public get statusDescription(): string | undefined {
|
||||
return this._statusDescription;
|
||||
}
|
||||
|
||||
public loadInformation(): Promise<void> {
|
||||
if (this._isInstalled) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
this._isInstalled = false;
|
||||
this._statusDescription = undefined;
|
||||
this._version = undefined;
|
||||
this._versionOutput = undefined;
|
||||
return this._platformService.runCommand(this.versionCommand).then((stdout) => {
|
||||
this._versionOutput = stdout;
|
||||
this._version = this.getVersionFromOutput(stdout);
|
||||
if (this._version) {
|
||||
this._isInstalled = true;
|
||||
} else {
|
||||
throw localize('deployCluster.InvalidToolVersionOutput', "Invalid output received.");
|
||||
}
|
||||
}).catch((error) => {
|
||||
const errorMessage = typeof error === 'string' ? error :
|
||||
typeof error.message === 'string' ? error.message : '';
|
||||
this._statusDescription = localize('deployCluster.GetToolVersionError', "Error retrieving version information.{0}Error: {1}{0}stdout: {2} ", EOL, errorMessage, this._versionOutput);
|
||||
});
|
||||
protected get installationCommands(): Command[] | undefined {
|
||||
return this.allInstallationCommands.get(this.osType);
|
||||
}
|
||||
|
||||
private _isInstalled: boolean = false;
|
||||
protected async getPip3InstallLocation(packageName: string): Promise<string> {
|
||||
const command = `pip3 show ${packageName}`;
|
||||
const pip3ShowOutput: string = await this._platformService.runCommand(command, { sudo: false, ignoreError: true });
|
||||
const installLocation = /^Location\: (.*)$/gim.exec(pip3ShowOutput);
|
||||
let retValue = installLocation && installLocation[1];
|
||||
if (retValue === undefined || retValue === null) {
|
||||
this.logToOutputChannel(` >${command}`); //command is localized by caller
|
||||
this.logToOutputChannel(localize('toolBase.getPip3InstallationLocation.LocationNotFound', " Could not find 'Location' in the output:"));
|
||||
this.logToOutputChannel(pip3ShowOutput, localize('toolBase.getPip3InstallationLocation.Output', " output:"));
|
||||
return '';
|
||||
} else {
|
||||
return retValue;
|
||||
}
|
||||
}
|
||||
|
||||
public get outputChannelName(): string {
|
||||
return this._platformService.outputChannelName();
|
||||
}
|
||||
|
||||
public showOutputChannel(preserveFocus?: boolean | undefined): void {
|
||||
this._platformService.showOutputChannel(preserveFocus);
|
||||
}
|
||||
|
||||
public async install(): Promise<void> {
|
||||
try {
|
||||
this.status = ToolStatus.Installing;
|
||||
await this.installCore();
|
||||
await this.addInstallationSearchPathsToSystemPath();
|
||||
this.status = await this.updateVersionAndGetStatus();
|
||||
} 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;
|
||||
throw error;
|
||||
}
|
||||
// Since we just completed installation, the status should be ToolStatus.Installed
|
||||
// but if it is ToolStatus.NotInstalled then it means that installation failed with 0 exit code.
|
||||
if (this.status === 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);
|
||||
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;
|
||||
throw new Error(this._statusDescription);
|
||||
}
|
||||
}
|
||||
|
||||
protected async installCore() {
|
||||
const installationCommands: Command[] | undefined = this.installationCommands;
|
||||
if (!installationCommands || installationCommands.length === 0) {
|
||||
throw new Error(localize('toolBase.installCore.CannotInstallTool', "Cannot install tool:${0}::${1} as installation commands are unknown", this.displayName, this.description));
|
||||
}
|
||||
for (let i: number = 0; i < installationCommands.length; i++) {
|
||||
await this._platformService.runCommand(installationCommands[i].command,
|
||||
{
|
||||
workingDirectory: installationCommands[i].workingDirectory || this.downloadPath,
|
||||
additionalEnvironmentVariables: installationCommands[i].additionalEnvironmentVariables,
|
||||
sudo: installationCommands[i].sudo,
|
||||
commandTitle: installationCommands[i].comment,
|
||||
ignoreError: installationCommands[i].ignoreError
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected async addInstallationSearchPathsToSystemPath(): Promise<void> {
|
||||
const installationPath = await this.getInstallationPath();
|
||||
const searchPaths = [installationPath, ...this.installationSearchPaths].filter(path => !!path);
|
||||
this.logToOutputChannel(localize('toolBase.addInstallationSearchPathsToSystemPath.SearchPaths', "Search Paths for tool '{0}': {1}", this.displayName, JSON.stringify(searchPaths, undefined, '\t'))); //this.displayName is localized and searchPaths are OS filesystem paths.
|
||||
searchPaths.forEach(installationSearchPath => {
|
||||
if (process.env.PATH) {
|
||||
if (!`${delimiter}${process.env.PATH}${delimiter}`.includes(`${delimiter}${installationSearchPath}${delimiter}`)) {
|
||||
process.env.PATH += `${delimiter}${installationSearchPath}`;
|
||||
console.log(`Appending to Path -> ${delimiter}${installationSearchPath}`);
|
||||
}
|
||||
} else {
|
||||
process.env.PATH = installationSearchPath;
|
||||
console.log(`Appending to Path -> '${delimiter}${installationSearchPath}':${delimiter}${installationSearchPath}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
public async loadInformation(): Promise<void> {
|
||||
if (this.status === ToolStatus.NotInstalled) {
|
||||
await this.addInstallationSearchPathsToSystemPath();
|
||||
this.status = await this.updateVersionAndGetStatus();
|
||||
}
|
||||
}
|
||||
private async updateVersionAndGetStatus(): Promise<ToolStatus> {
|
||||
const commandOutput = await this._platformService.runCommand(
|
||||
this.versionCommand.command,
|
||||
{
|
||||
workingDirectory: this.versionCommand.workingDirectory,
|
||||
additionalEnvironmentVariables: this.versionCommand.additionalEnvironmentVariables,
|
||||
sudo: false,
|
||||
ignoreError: true
|
||||
},
|
||||
);
|
||||
this.version = this.getVersionFromOutput(commandOutput);
|
||||
if (this.version) {
|
||||
return ToolStatus.Installed;
|
||||
}
|
||||
else {
|
||||
this._statusDescription = localize('deployCluster.GetToolVersionError', "Error retrieving version information.{0}Invalid output received, get version command output: '{1}' ", EOL, commandOutput);
|
||||
return ToolStatus.NotInstalled;
|
||||
}
|
||||
}
|
||||
|
||||
private _storagePathEnsured: boolean = false;
|
||||
private _status: ToolStatus = ToolStatus.NotInstalled;
|
||||
private _osType: OsType;
|
||||
private _version?: SemVer;
|
||||
private _statusDescription?: string;
|
||||
private _versionOutput?: string;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user