wizard for deploying bdc (#7183)

* wip

* wip2

* wip eod 820

* wip 822

* text component improvements and misc changes

* aria-label

* targetClusterPage wip

* target cluster page

* target cluster page

* wip 827

* wip deployment profile page

* profile page

* service settings page

* wip 0903

* 0909 wip

* 0910

* 0911

* sql instance and working directory

* notebooks

* docker version on windows

* EULA env var

* 917 updates

* address comments

* use async file access

* fix the summary page display issue for ad auth

* add save json file buttons

* use promise for private methds

* review feedbacks

* refactor

* pass json to notebooks

* fix no tool scenario

* bypass tool check if installed

* update hint text

* update notebooks

* workaround azdata first time use

* comments

* accept eula and some text update

* fix the error in package.json

* promise instead of thenable

* comments

* fix typo
This commit is contained in:
Alan Ren
2019-09-25 10:04:13 -07:00
committed by GitHub
parent 6a6048d40f
commit a0e31fc723
51 changed files with 4137 additions and 855 deletions

View File

@@ -0,0 +1,59 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import { IPlatformService } from './platformService';
import { BigDataClusterDeploymentProfile } from './bigDataClusterDeploymentProfile';
interface BdcConfigListOutput {
stdout: string[];
}
export interface IAzdataService {
getDeploymentProfiles(): Promise<BigDataClusterDeploymentProfile[]>;
}
export class AzdataService implements IAzdataService {
constructor(private platformService: IPlatformService) {
}
public async getDeploymentProfiles(): Promise<BigDataClusterDeploymentProfile[]> {
await this.ensureWorkingDirectoryExists();
const profileNames = await this.getDeploymentProfileNames();
return await Promise.all(profileNames.map(profile => this.getDeploymentProfileInfo(profile)));
}
private async getDeploymentProfileNames(): Promise<string[]> {
const env: NodeJS.ProcessEnv = {};
// azdata requires this environment variables to be set
env['ACCEPT_EULA'] = 'yes';
const cmd = 'azdata bdc config list -o json';
// Run the command twice to workaround the issue:
// First time use of the azdata will have extra EULA related string in the output
// there is no easy and reliable way to filter out the profile names from it.
await this.platformService.runCommand(cmd, { additionalEnvironmentVariables: env });
const stdout = await this.platformService.runCommand(cmd);
const output = <BdcConfigListOutput>JSON.parse(stdout);
return output.stdout;
}
private async getDeploymentProfileInfo(profileName: string): Promise<BigDataClusterDeploymentProfile> {
await this.platformService.runCommand(`azdata bdc config init --source ${profileName} --target ${profileName} --force`, { workingDirectory: this.platformService.storagePath() });
const configObjects = await Promise.all([
this.getJsonObjectFromFile(path.join(this.platformService.storagePath(), profileName, 'bdc.json')),
this.getJsonObjectFromFile(path.join(this.platformService.storagePath(), profileName, 'control.json'))
]);
return new BigDataClusterDeploymentProfile(profileName, configObjects[0], configObjects[1]);
}
private async ensureWorkingDirectoryExists(): Promise<void> {
if (! await this.platformService.fileExists(this.platformService.storagePath())) {
await this.platformService.makeDirectory(this.platformService.storagePath());
}
}
private async getJsonObjectFromFile(path: string): Promise<any> {
return JSON.parse(await this.platformService.readTextFile(path));
}
}

View File

@@ -0,0 +1,264 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const SqlServerMasterResource = 'master';
export const DataResource = 'data-0';
export const HdfsResource = 'storage-0';
export const ComputeResource = 'compute-0';
export const NameNodeResource = 'nmnode-0';
export const SparkHeadResource = 'sparkhead';
export const ZooKeeperResource = 'zookeeper';
export const SparkResource = 'spark-0';
export const HadrEnabledSetting = 'hadr.enabled';
interface ServiceEndpoint {
port: number;
serviceType: ServiceType;
name: EndpointName;
}
type ServiceType = 'NodePort' | 'LoadBalancer';
type EndpointName = 'Controller' | 'Master' | 'Knox' | 'MasterSecondary';
export class BigDataClusterDeploymentProfile {
constructor(private _profileName: string, private _bdcConfig: any, private _controlConfig: any) {
// TODO: add validation logic for these 2 objects
// https://github.com/microsoft/azuredatastudio/issues/7344
}
public get profileName(): string {
return this._profileName;
}
public get clusterName(): string {
return this._bdcConfig.metadata.name;
}
public set clusterName(value: string) {
this._bdcConfig.metadata.name = value;
}
public get bdcConfig(): any {
return this._bdcConfig;
}
public get controlConfig(): any {
return this._controlConfig;
}
public get sqlServerReplicas(): number {
return this.getReplicas(SqlServerMasterResource);
}
public set sqlServerReplicas(replicas: number) {
this.setReplicas(SqlServerMasterResource, replicas);
}
public get hdfsNameNodeReplicas(): number {
return this.getReplicas(NameNodeResource);
}
public set hdfsNameNodeReplicas(replicas: number) {
this.setReplicas(NameNodeResource, replicas);
}
public get sparkHeadReplicas(): number {
return this.getReplicas(SparkHeadResource);
}
public set sparkHeadReplicas(replicas: number) {
this.setReplicas(SparkHeadResource, replicas);
}
public get dataReplicas(): number {
return this.getReplicas(DataResource);
}
public set dataReplicas(replicas: number) {
this.setReplicas(SparkHeadResource, replicas);
}
public get hdfsReplicas(): number {
return this.getReplicas(HdfsResource);
}
public set hdfsReplicas(replicas: number) {
this.setReplicas(HdfsResource, replicas);
}
public get zooKeeperReplicas(): number {
return this.getReplicas(ZooKeeperResource);
}
public set zooKeeperReplicas(replicas: number) {
this.setReplicas(ZooKeeperResource, replicas);
}
public get computeReplicas(): number {
return this.getReplicas(ComputeResource);
}
public set computeReplicas(replicas: number) {
this.setReplicas(ComputeResource, replicas);
}
public get sparkReplicas(): number {
return this._bdcConfig.spec.resources[SparkResource] ? this.getReplicas(SparkResource) : 0;
}
public get hadrEnabled(): boolean {
const value = this._bdcConfig.spec.resources[SqlServerMasterResource].spec.settings.sql[HadrEnabledSetting];
return value === true || value === 'true';
}
public set hadrEnabled(value: boolean) {
this._bdcConfig.spec.resources[SqlServerMasterResource].spec.settings.sql[HadrEnabledSetting] = value;
}
public get includeSpark(): boolean {
return <boolean>this._bdcConfig.spec.resources[HdfsResource].spec.settings.spark.includeSpark;
}
public set includeSpark(value: boolean) {
this._bdcConfig.spec.resources[HdfsResource].spec.settings.spark.includeSpark = value;
}
public get controllerDataStorageClass(): string {
return <string>this._controlConfig.spec.storage.data.className;
}
public set controllerDataStorageClass(value: string) {
this._controlConfig.spec.storage.data.className = value;
}
public get controllerDataStorageSize(): number {
return <number>this._controlConfig.spec.storage.data.size.replace('Gi', '');
}
public set controllerDataStorageSize(value: number) {
this._controlConfig.spec.storage.data.size = value;
}
public get controllerLogsStorageClass(): string {
return <string>this._controlConfig.spec.storage.logs.className;
}
public set controllerLogsStorageClass(value: string) {
this._controlConfig.spec.storage.logs.className = value;
}
public get controllerLogsStorageSize(): number {
return <number>this._controlConfig.spec.storage.logs.size.replace('Gi', '');
}
public set controllerLogsStorageSize(value: number) {
this._controlConfig.spec.storage.logs.size = value;
}
public setResourceStorage(resourceName: 'data-0' | 'master' | 'storage-0', dataStorageClass: string, dataStorageSize: number, logsStorageClass: string, logsStorageSize: number) {
this.bdcConfig.spec.resources[resourceName]['storage'] = {
data: {
size: `${dataStorageSize}Gi`,
className: dataStorageClass,
accessMode: 'ReadWriteOnce'
},
logs: {
size: `${logsStorageSize}Gi`,
className: logsStorageClass,
accessMode: 'ReadWriteOnce'
}
};
}
public get controllerPort(): number {
return this.getEndpointPort(this._controlConfig.spec.endpoints, 'Controller', 30080);
}
public set controllerPort(port: number) {
this.setEndpointPort(this._controlConfig.spec.endpoints, 'Controller', port);
}
public get sqlServerPort(): number {
return this.getEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'Master', 31433);
}
public set sqlServerPort(port: number) {
this.setEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'Master', port);
}
public get sqlServerReadableSecondaryPort(): number {
return this.getEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'MasterSecondary', 31436);
}
public set sqlServerReadableSecondaryPort(port: number) {
this.setEndpointPort(this._bdcConfig.spec.resources.master.spec.endpoints, 'MasterSecondary', port);
}
public get gatewayPort(): number {
return this.getEndpointPort(this._bdcConfig.spec.resources.gateway.spec.endpoints, 'Knox', 30443);
}
public set gatewayPort(port: number) {
this.setEndpointPort(this._bdcConfig.spec.resources.gateway.spec.endpoints, 'Knox', port);
}
public addSparkResource(replicas: number): void {
this._bdcConfig.spec.resources[SparkResource] = {
metadata: {
kind: 'Pool',
name: 'default'
},
spec: {
type: 'Spark',
replicas: replicas
}
};
this._bdcConfig.spec.services.spark.resources.push(SparkResource);
this._bdcConfig.spec.services.hdfs.resources.push(SparkResource);
}
public get activeDirectorySupported(): boolean {
// TODO: Implement AD authentication
return false;
}
public getBdcJson(readable: boolean = true): string {
return this.stringifyJson(this._bdcConfig, readable);
}
public getControlJson(readable: boolean = true): string {
return this.stringifyJson(this._controlConfig, readable);
}
private stringifyJson(obj: any, readable: boolean): string {
return JSON.stringify(obj, undefined, readable ? 4 : 0);
}
private getReplicas(resourceName: string): number {
return <number>this._bdcConfig.spec.resources[resourceName].spec.replicas;
}
private setReplicas(resourceName: string, replicas: number): void {
this._bdcConfig.spec.resources[resourceName].spec.replicas = replicas;
}
private getEndpointPort(endpoints: ServiceEndpoint[], name: EndpointName, defaultValue: number): number {
const endpoint = endpoints.find(endpoint => endpoint.name === name);
return endpoint ? endpoint.port : defaultValue;
}
private setEndpointPort(endpoints: ServiceEndpoint[], name: EndpointName, port: number): void {
const endpoint = endpoints.find(endpoint => endpoint.name === name);
if (endpoint) {
endpoint.port = port;
} else {
endpoints.push({
name: name,
serviceType: 'NodePort',
port: port
});
}
}
}

View File

@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as path from 'path';
import * as os from 'os';
import * as yamljs from 'yamljs';
import * as fs from 'fs';
export interface KubeClusterContext {
name: string;
isCurrentContext: boolean;
}
export interface IKubeService {
getDefautConfigPath(): string;
getClusterContexts(configFile: string): Promise<KubeClusterContext[]>;
}
export class KubeService implements IKubeService {
getDefautConfigPath(): string {
return path.join(os.homedir(), '.kube', 'config');
}
getClusterContexts(configFile: string): Promise<KubeClusterContext[]> {
return fs.promises.access(configFile).catch((error) => {
if (error && error.code === 'ENOENT') {
return [];
} else {
throw error;
}
}).then(() => {
const config = yamljs.load(configFile);
const rawContexts = <any[]>config['contexts'];
const currentContext = <string>config['current-context'];
const contexts: KubeClusterContext[] = [];
if (currentContext && rawContexts && rawContexts.length > 0) {
rawContexts.forEach(rawContext => {
const name = <string>rawContext['name'];
if (name) {
contexts.push({
name: name,
isCurrentContext: name === currentContext
});
}
});
}
return contexts;
});
}
}

View File

@@ -2,7 +2,6 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as path from 'path';
@@ -14,7 +13,7 @@ import { NotebookInfo } from '../interfaces';
const localize = nls.loadMessageBundle();
export interface INotebookService {
launchNotebook(notebook: string | NotebookInfo): void;
launchNotebook(notebook: string | NotebookInfo): Thenable<azdata.nb.NotebookEditor>;
}
export class NotebookService implements INotebookService {
@@ -25,18 +24,22 @@ export class NotebookService implements INotebookService {
* Copy the notebook to the user's home directory and launch the notebook from there.
* @param notebook the path of the notebook
*/
launchNotebook(notebook: string | NotebookInfo): void {
launchNotebook(notebook: string | NotebookInfo): Thenable<azdata.nb.NotebookEditor> {
const notebookPath = this.getNotebook(notebook);
const notebookFullPath = path.join(this.extensionPath, notebookPath);
if (notebookPath && this.platformService.fileExists(notebookPath)) {
this.showNotebookAsUntitled(notebookPath);
}
else if (notebookPath && this.platformService.fileExists(notebookFullPath)) {
this.showNotebookAsUntitled(notebookFullPath);
}
else {
this.platformService.showErrorMessage(localize('resourceDeployment.notebookNotFound', "The notebook {0} does not exist", notebookPath));
}
return this.platformService.fileExists(notebookPath).then((notebookPathExists) => {
if (notebookPathExists) {
return this.showNotebookAsUntitled(notebookPath);
} else {
return this.platformService.fileExists(notebookFullPath).then(notebookFullPathExists => {
if (notebookFullPathExists) {
return this.showNotebookAsUntitled(notebookFullPath);
} else {
throw localize('resourceDeployment.notebookNotFound', "The notebook {0} does not exist", notebookPath);
}
});
}
});
}
/**
@@ -74,12 +77,12 @@ export class NotebookService implements INotebookService {
return title;
}
showNotebookAsUntitled(notebookPath: string): void {
showNotebookAsUntitled(notebookPath: string): Thenable<azdata.nb.NotebookEditor> {
let targetFileName: string = this.findNextUntitledEditorName(notebookPath);
const untitledFileName: vscode.Uri = vscode.Uri.parse(`untitled:${targetFileName}`);
vscode.workspace.openTextDocument(notebookPath).then((document) => {
return vscode.workspace.openTextDocument(notebookPath).then((document) => {
let initialContent = document.getText();
azdata.nb.showNotebookDocument(untitledFileName, {
return azdata.nb.showNotebookDocument(untitledFileName, {
connectionProfile: undefined,
preview: false,
initialContent: initialContent,

View File

@@ -2,37 +2,58 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as fs from 'fs';
import * as vscode from 'vscode';
import * as azdata from 'azdata';
import * as cp from 'child_process';
/**
* Abstract of platform dependencies
*/
export interface IPlatformService {
platform(): string;
copyFile(source: string, target: string): void;
fileExists(file: string): boolean;
storagePath(): string;
copyFile(source: string, target: string): Promise<void>;
fileExists(file: string): Promise<boolean>;
openFile(filePath: string): void;
showErrorMessage(message: string): void;
isNotebookNameUsed(title: string): boolean;
makeDirectory(path: string): Promise<void>;
readTextFile(filePath: string): Promise<string>;
runCommand(command: string, options?: CommandOptions): Promise<string>;
}
export interface CommandOptions {
workingDirectory?: string;
additionalEnvironmentVariables?: NodeJS.ProcessEnv;
}
export class PlatformService implements IPlatformService {
constructor(private _storagePath: string = '') {
}
storagePath(): string {
return this._storagePath;
}
platform(): string {
return process.platform;
}
copyFile(source: string, target: string): void {
// tslint:disable-next-line:no-sync
fs.copyFileSync(source, target);
copyFile(source: string, target: string): Promise<void> {
return fs.promises.copyFile(source, target);
}
fileExists(file: string): boolean {
// tslint:disable-next-line:no-sync
return fs.existsSync(file);
fileExists(file: string): Promise<boolean> {
return fs.promises.access(file).then(() => {
return true;
}).catch(error => {
if (error && error.code === 'ENOENT') {
return false;
}
throw error;
});
}
openFile(filePath: string): void {
@@ -46,4 +67,28 @@ export class PlatformService implements IPlatformService {
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);
}
readTextFile(filePath: string): Promise<string> {
return fs.promises.readFile(filePath, 'utf8');
}
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) => {
if (error) {
reject(error);
} else {
resolve(stdout);
}
});
});
}
}

View File

@@ -2,7 +2,6 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as cp from 'child_process';
@@ -16,7 +15,10 @@ import { INotebookService } from './notebookService';
import { IPlatformService } from './platformService';
import { IToolsService } from './toolsService';
import { ResourceType, ResourceTypeOption, DeploymentProvider } from '../interfaces';
import { DeployClusterWizard } from '../ui/deployClusterWizard/deployClusterWizard';
import { NotebookInputDialog } from '../ui/notebookInputDialog';
import { KubeService } from './kubeService';
import { AzdataService } from './azdataService';
const localize = nls.loadMessageBundle();
export interface IResourceTypeService {
@@ -138,7 +140,7 @@ export class ResourceTypeService implements IResourceTypeService {
let providerIndex = 1;
resourceType.providers.forEach(provider => {
const providerPositionInfo = `${positionInfo}, provider index: ${providerIndex} `;
if (!provider.dialog && !provider.notebook && !provider.downloadUrl && !provider.webPageUrl) {
if (!provider.wizard && !provider.dialog && !provider.notebook && !provider.downloadUrl && !provider.webPageUrl) {
errorMessages.push(`No deployment method defined for the provider, ${providerPositionInfo}`);
}
@@ -195,7 +197,10 @@ export class ResourceTypeService implements IResourceTypeService {
public startDeployment(provider: DeploymentProvider): void {
const self = this;
if (provider.dialog) {
if (provider.wizard) {
const wizard = new DeployClusterWizard(provider.wizard, new KubeService(), new AzdataService(this.platformService), this.notebookService);
wizard.open();
} else if (provider.dialog) {
const dialog = new NotebookInputDialog(this.notebookService, provider.dialog);
dialog.open();
} else if (provider.notebook) {

View File

@@ -2,12 +2,20 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ToolType, ITool } from '../../interfaces';
import { ToolType } from '../../interfaces';
import * as nls from 'vscode-nls';
import { SemVer } from 'semver';
import { IPlatformService } from '../platformService';
import { EOL } from 'os';
import { ToolBase } from './toolBase';
const localize = nls.loadMessageBundle();
export class AzCliTool implements ITool {
export class AzCliTool extends ToolBase {
constructor(platformService: IPlatformService) {
super(platformService);
}
get name(): string {
return 'azcli';
}
@@ -23,4 +31,19 @@ export class AzCliTool implements ITool {
get displayName(): string {
return localize('resourceDeployment.AzCLIDisplayName', 'Azure CLI');
}
get homePage(): string {
return 'https://docs.microsoft.com/cli/azure/install-azure-cli';
}
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('*', ''));
} else {
return undefined;
}
}
protected get versionCommand(): string {
return 'az --version';
}
}

View File

@@ -2,12 +2,21 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ToolType, ITool } from '../../interfaces';
import { ToolType } from '../../interfaces';
import * as nls from 'vscode-nls';
import { SemVer } from 'semver';
import { EOL } from 'os';
import { IPlatformService } from '../platformService';
import { ToolBase } from './toolBase';
const localize = nls.loadMessageBundle();
export class AzdataTool implements ITool {
export class AzdataTool extends ToolBase {
constructor(platformService: IPlatformService) {
super(platformService);
}
get name(): string {
return 'azdata';
}
@@ -23,4 +32,20 @@ export class AzdataTool implements ITool {
get displayName(): string {
return localize('resourceDeployment.AzdataDisplayName', "azdata");
}
}
get homePage(): string {
return 'https://docs.microsoft.com/sql/big-data-cluster/deploy-install-azdata';
}
protected get versionCommand(): string {
return 'azdata -v';
}
protected getVersionFromOutput(output: string): SemVer | undefined {
let version: SemVer | undefined = undefined;
if (output && output.split(EOL).length > 0) {
version = new SemVer(output.split(EOL)[0].replace(/ /g, ''));
}
return version;
}
}

View File

@@ -2,12 +2,20 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ToolType, ITool } from '../../interfaces';
import { ToolType } from '../../interfaces';
import * as nls from 'vscode-nls';
import { SemVer } from 'semver';
import { IPlatformService } from '../platformService';
import { ToolBase } from './toolBase';
const localize = nls.loadMessageBundle();
export class DockerTool implements ITool {
export class DockerTool extends ToolBase {
constructor(platformService: IPlatformService) {
super(platformService);
}
get name(): string {
return 'docker';
}
@@ -23,4 +31,19 @@ export class DockerTool implements ITool {
get displayName(): string {
return localize('resourceDeployment.DockerDisplayName', 'Docker');
}
}
get homePage(): string {
return 'https://docs.docker.com/install';
}
protected getVersionFromOutput(output: string): SemVer | undefined {
let version: SemVer | undefined = undefined;
if (output) {
version = new SemVer(JSON.parse(output).Client.Version, true);
}
return version;
}
protected get versionCommand(): string {
return 'docker version --format "{{json .}}"';
}
}

View File

@@ -2,12 +2,20 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ToolType, ITool } from '../../interfaces';
import { ToolType } from '../../interfaces';
import * as nls from 'vscode-nls';
import { SemVer } from 'semver';
import { IPlatformService } from '../platformService';
import { ToolBase } from './toolBase';
const localize = nls.loadMessageBundle();
export class KubeCtlTool implements ITool {
export class KubeCtlTool extends ToolBase {
constructor(platformService: IPlatformService) {
super(platformService);
}
get name(): string {
return 'kubectl';
}
@@ -23,4 +31,21 @@ export class KubeCtlTool implements ITool {
get displayName(): string {
return localize('resourceDeployment.KubeCtlDisplayName', 'kubectl');
}
get homePage(): string {
return 'https://kubernetes.io/docs/tasks/tools/install-kubectl';
}
protected getVersionFromOutput(output: string): SemVer | undefined {
let version: SemVer | undefined = undefined;
if (output) {
const versionJson = JSON.parse(output);
version = new SemVer(`${versionJson.clientVersion.major}.${versionJson.clientVersion.minor}.0`);
}
return version;
}
protected get versionCommand(): string {
return 'kubectl version -o json --client';
}
}

View File

@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* 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';
const localize = nls.loadMessageBundle();
export abstract class ToolBase implements ITool {
constructor(private _platformService: IPlatformService) { }
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;
public get version(): SemVer | undefined {
return this._version;
}
public get isInstalled(): boolean {
return this._isInstalled;
}
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);
});
}
private _isInstalled: boolean = false;
private _version?: SemVer;
private _statusDescription?: string;
private _versionOutput?: string;
}

View File

@@ -2,12 +2,12 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ITool } from '../interfaces';
import { DockerTool } from './tools/dockerTool';
import { AzCliTool } from './tools/azCliTool';
import { AzdataTool } from './tools/azdataTool';
import { KubeCtlTool } from './tools/kubeCtlTool';
import { IPlatformService } from './platformService';
export interface IToolsService {
getToolByName(toolName: string): ITool | undefined;
@@ -16,11 +16,11 @@ export interface IToolsService {
export class ToolsService implements IToolsService {
private supportedTools: ITool[];
constructor() {
this.supportedTools = [new DockerTool(), new AzCliTool(), new AzdataTool(), new KubeCtlTool()];
constructor(private _platformService: IPlatformService) {
this.supportedTools = [new DockerTool(this._platformService), new AzCliTool(this._platformService), new AzdataTool(this._platformService), new KubeCtlTool(this._platformService)];
}
getToolByName(toolName: string): ITool | undefined {
return this.supportedTools.find(t => t.name === toolName);
}
}
}