mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Feature/mssql-big-data-cluster (#4107)
* Adding kubernetes installer. * Adding variety of kubectl support and integrating into the kubeconfig target cluster page. * Addressing PR comments, refactored utility file locations and added missing license headers.
This commit is contained in:
121
extensions/big-data-cluster/src/config/config.ts
Normal file
121
extensions/big-data-cluster/src/config/config.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { Host } from '../kubectl/host';
|
||||
import { Shell, Platform } from '../utility/shell';
|
||||
|
||||
const EXTENSION_CONFIG_KEY = "mssql-bdc";
|
||||
const KUBECONFIG_PATH_KEY = "mssql-bdc.kubeconfig";
|
||||
const KNOWN_KUBECONFIGS_KEY = "mssql-bdc.knownKubeconfigs";
|
||||
|
||||
export async function addPathToConfig(configKey: string, value: string): Promise<void> {
|
||||
await setConfigValue(configKey, value);
|
||||
}
|
||||
|
||||
async function setConfigValue(configKey: string, value: any): Promise<void> {
|
||||
await atAllConfigScopes(addValueToConfigAtScope, configKey, value);
|
||||
}
|
||||
|
||||
async function addValueToConfigAtScope(configKey: string, value: any, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise<void> {
|
||||
if (!createIfNotExist) {
|
||||
if (!valueAtScope || !(valueAtScope[configKey])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let newValue: any = {};
|
||||
if (valueAtScope) {
|
||||
newValue = Object.assign({}, valueAtScope);
|
||||
}
|
||||
newValue[configKey] = value;
|
||||
await vscode.workspace.getConfiguration().update(EXTENSION_CONFIG_KEY, newValue, scope);
|
||||
}
|
||||
|
||||
async function addValueToConfigArray(configKey: string, value: string): Promise<void> {
|
||||
await atAllConfigScopes(addValueToConfigArrayAtScope, configKey, value);
|
||||
}
|
||||
|
||||
async function addValueToConfigArrayAtScope(configKey: string, value: string, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean): Promise<void> {
|
||||
if (!createIfNotExist) {
|
||||
if (!valueAtScope || !(valueAtScope[configKey])) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let newValue: any = {};
|
||||
if (valueAtScope) {
|
||||
newValue = Object.assign({}, valueAtScope);
|
||||
}
|
||||
const arrayEntry: string[] = newValue[configKey] || [];
|
||||
arrayEntry.push(value);
|
||||
newValue[configKey] = arrayEntry;
|
||||
await vscode.workspace.getConfiguration().update(EXTENSION_CONFIG_KEY, newValue, scope);
|
||||
}
|
||||
|
||||
type ConfigUpdater<T> = (configKey: string, value: T, scope: vscode.ConfigurationTarget, valueAtScope: any, createIfNotExist: boolean) => Promise<void>;
|
||||
|
||||
async function atAllConfigScopes<T>(fn: ConfigUpdater<T>, configKey: string, value: T): Promise<void> {
|
||||
const config = vscode.workspace.getConfiguration().inspect(EXTENSION_CONFIG_KEY)!;
|
||||
await fn(configKey, value, vscode.ConfigurationTarget.Global, config.globalValue, true);
|
||||
await fn(configKey, value, vscode.ConfigurationTarget.Workspace, config.workspaceValue, false);
|
||||
await fn(configKey, value, vscode.ConfigurationTarget.WorkspaceFolder, config.workspaceFolderValue, false);
|
||||
}
|
||||
|
||||
// Functions for working with the list of known kubeconfigs
|
||||
|
||||
export function getKnownKubeconfigs(): string[] {
|
||||
const kkcConfig = vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)[KNOWN_KUBECONFIGS_KEY];
|
||||
if (!kkcConfig || !kkcConfig.length) {
|
||||
return [];
|
||||
}
|
||||
return kkcConfig as string[];
|
||||
}
|
||||
|
||||
export async function addKnownKubeconfig(kubeconfigPath: string) {
|
||||
await addValueToConfigArray(KNOWN_KUBECONFIGS_KEY, kubeconfigPath);
|
||||
}
|
||||
|
||||
// Functions for working with the active kubeconfig setting
|
||||
|
||||
export async function setActiveKubeconfig(kubeconfig: string): Promise<void> {
|
||||
await addPathToConfig(KUBECONFIG_PATH_KEY, kubeconfig);
|
||||
}
|
||||
|
||||
export function getActiveKubeconfig(): string {
|
||||
return vscode.workspace.getConfiguration(EXTENSION_CONFIG_KEY)[KUBECONFIG_PATH_KEY];
|
||||
}
|
||||
|
||||
// Functions for working with tool paths
|
||||
|
||||
export function getToolPath(host: Host, shell: Shell, tool: string): string | undefined {
|
||||
const baseKey = toolPathBaseKey(tool);
|
||||
return getPathSetting(host, shell, baseKey);
|
||||
}
|
||||
|
||||
function getPathSetting(host: Host, shell: Shell, baseKey: string): string | undefined {
|
||||
const os = shell.platform();
|
||||
const osOverridePath = host.getConfiguration(EXTENSION_CONFIG_KEY)[osOverrideKey(os, baseKey)];
|
||||
return osOverridePath || host.getConfiguration(EXTENSION_CONFIG_KEY)[baseKey];
|
||||
}
|
||||
|
||||
export function toolPathBaseKey(tool: string): string {
|
||||
return `mssql-bdc.${tool}-path`;
|
||||
}
|
||||
|
||||
function osOverrideKey(os: Platform, baseKey: string): string {
|
||||
const osKey = osKeyString(os);
|
||||
return osKey ? `${baseKey}.${osKey}` : baseKey; // The 'else' clause should never happen so don't worry that this would result in double-checking a missing base key
|
||||
}
|
||||
|
||||
function osKeyString(os: Platform): string | null {
|
||||
switch (os) {
|
||||
case Platform.Windows: return 'windows';
|
||||
case Platform.MacOS: return 'mac';
|
||||
case Platform.Linux: return 'linux';
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
45
extensions/big-data-cluster/src/installer/download.ts
Normal file
45
extensions/big-data-cluster/src/installer/download.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 stream from 'stream';
|
||||
import * as tmp from 'tmp';
|
||||
|
||||
import { succeeded, Errorable } from '../interfaces';
|
||||
|
||||
type DownloadFunc =
|
||||
(url: string, destination?: string, options?: any)
|
||||
=> Promise<Buffer> & stream.Duplex; // Stream has additional events - see https://www.npmjs.com/package/download
|
||||
|
||||
let download: DownloadFunc;
|
||||
|
||||
function ensureDownloadFunc() {
|
||||
if (!download) {
|
||||
const home = process.env['HOME'];
|
||||
download = require('download');
|
||||
if (home) {
|
||||
process.env['HOME'] = home;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function toTempFile(sourceUrl: string): Promise<Errorable<string>> {
|
||||
const tempFileObj = tmp.fileSync({ prefix: "mssql-bdc-autoinstall-" });
|
||||
const downloadResult = await to(sourceUrl, tempFileObj.name);
|
||||
if (succeeded(downloadResult)) {
|
||||
return { succeeded: true, result: tempFileObj.name };
|
||||
}
|
||||
return { succeeded: false, error: downloadResult.error };
|
||||
}
|
||||
|
||||
export async function to(sourceUrl: string, destinationFile: string): Promise<Errorable<null>> {
|
||||
ensureDownloadFunc();
|
||||
try {
|
||||
await download(sourceUrl, path.dirname(destinationFile), { filename: path.basename(destinationFile) });
|
||||
return { succeeded: true, result: null };
|
||||
} catch (e) {
|
||||
return { succeeded: false, error: [e.message] };
|
||||
}
|
||||
}
|
||||
69
extensions/big-data-cluster/src/installer/installer.ts
Normal file
69
extensions/big-data-cluster/src/installer/installer.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 download from './download';
|
||||
import * as fs from 'fs';
|
||||
import mkdirp = require('mkdirp');
|
||||
import * as path from 'path';
|
||||
import { Shell, Platform } from '../utility/shell';
|
||||
import { Errorable, failed } from '../interfaces';
|
||||
import { addPathToConfig, toolPathBaseKey } from '../config/config';
|
||||
|
||||
export async function installKubectl(shell: Shell): Promise<Errorable<null>> {
|
||||
const tool = 'kubectl';
|
||||
const binFile = (shell.isUnix()) ? 'kubectl' : 'kubectl.exe';
|
||||
const os = platformUrlString(shell.platform());
|
||||
|
||||
const version = await getStableKubectlVersion();
|
||||
if (failed(version)) {
|
||||
return { succeeded: false, error: version.error };
|
||||
}
|
||||
|
||||
const installFolder = getInstallFolder(shell, tool);
|
||||
mkdirp.sync(installFolder);
|
||||
|
||||
const kubectlUrl = `https://storage.googleapis.com/kubernetes-release/release/${version.result.trim()}/bin/${os}/amd64/${binFile}`;
|
||||
const downloadFile = path.join(installFolder, binFile);
|
||||
const downloadResult = await download.to(kubectlUrl, downloadFile);
|
||||
if (failed(downloadResult)) {
|
||||
return { succeeded: false, error: [`Failed to download kubectl: ${downloadResult.error[0]}`] };
|
||||
}
|
||||
|
||||
if (shell.isUnix()) {
|
||||
fs.chmodSync(downloadFile, '0777');
|
||||
}
|
||||
|
||||
await addPathToConfig(toolPathBaseKey(tool), downloadFile);
|
||||
return { succeeded: true, result: null };
|
||||
}
|
||||
|
||||
async function getStableKubectlVersion(): Promise<Errorable<string>> {
|
||||
const downloadResult = await download.toTempFile('https://storage.googleapis.com/kubernetes-release/release/stable.txt');
|
||||
if (failed(downloadResult)) {
|
||||
return { succeeded: false, error: [`Failed to establish kubectl stable version: ${downloadResult.error[0]}`] };
|
||||
}
|
||||
const version = fs.readFileSync(downloadResult.result, 'utf-8');
|
||||
fs.unlinkSync(downloadResult.result);
|
||||
return { succeeded: true, result: version };
|
||||
}
|
||||
|
||||
export function getInstallFolder(shell: Shell, tool: string): string {
|
||||
return path.join(shell.home(), `.mssql-bdc/tools/${tool}`);
|
||||
}
|
||||
|
||||
function platformUrlString(platform: Platform, supported?: Platform[]): string | null {
|
||||
if (supported && supported.indexOf(platform) < 0) {
|
||||
return null;
|
||||
}
|
||||
switch (platform) {
|
||||
case Platform.Windows: return 'windows';
|
||||
case Platform.MacOS: return 'darwin';
|
||||
case Platform.Linux: return 'linux';
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,25 @@ export enum TargetClusterType {
|
||||
NewAksCluster
|
||||
}
|
||||
|
||||
export interface Succeeded<T> {
|
||||
readonly succeeded: true;
|
||||
readonly result: T;
|
||||
}
|
||||
|
||||
export interface Failed {
|
||||
readonly succeeded: false;
|
||||
readonly error: string[];
|
||||
}
|
||||
|
||||
export type Errorable<T> = Succeeded<T> | Failed;
|
||||
|
||||
export function succeeded<T>(e: Errorable<T>): e is Succeeded<T> {
|
||||
return e.succeeded;
|
||||
}
|
||||
|
||||
export function failed<T>(e: Errorable<T>): e is Failed {
|
||||
return !e.succeeded;
|
||||
}
|
||||
export interface ClusterPorts {
|
||||
sql: string;
|
||||
knox: string;
|
||||
@@ -43,4 +62,4 @@ export interface ToolInfo {
|
||||
name: string,
|
||||
description: string,
|
||||
isInstalled: boolean
|
||||
}
|
||||
}
|
||||
|
||||
117
extensions/big-data-cluster/src/kubectl/binutil.ts
Normal file
117
extensions/big-data-cluster/src/kubectl/binutil.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Shell } from '../utility/shell';
|
||||
import { Host } from './host';
|
||||
import { FS } from '../utility/fs';
|
||||
|
||||
export interface BinCheckContext {
|
||||
readonly host: Host;
|
||||
readonly fs: FS;
|
||||
readonly shell: Shell;
|
||||
readonly installDependenciesCallback: () => void;
|
||||
binFound: boolean;
|
||||
binPath: string;
|
||||
}
|
||||
|
||||
interface FindBinaryResult {
|
||||
err: number | null;
|
||||
output: string;
|
||||
}
|
||||
|
||||
async function findBinary(shell: Shell, binName: string): Promise<FindBinaryResult> {
|
||||
let cmd = `which ${binName}`;
|
||||
|
||||
if (shell.isWindows()) {
|
||||
cmd = `where.exe ${binName}.exe`;
|
||||
}
|
||||
|
||||
const opts = {
|
||||
async: true,
|
||||
env: {
|
||||
HOME: process.env.HOME,
|
||||
PATH: process.env.PATH
|
||||
}
|
||||
};
|
||||
|
||||
const execResult = await shell.execCore(cmd, opts);
|
||||
if (execResult.code) {
|
||||
return { err: execResult.code, output: execResult.stderr };
|
||||
}
|
||||
|
||||
return { err: null, output: execResult.stdout };
|
||||
}
|
||||
|
||||
export function execPath(shell: Shell, basePath: string): string {
|
||||
let bin = basePath;
|
||||
if (shell.isWindows() && bin && !(bin.endsWith('.exe'))) {
|
||||
bin = bin + '.exe';
|
||||
}
|
||||
return bin;
|
||||
}
|
||||
|
||||
type CheckPresentFailureReason = 'inferFailed' | 'configuredFileMissing';
|
||||
|
||||
function alertNoBin(host: Host, binName: string, failureReason: CheckPresentFailureReason, message: string, installDependencies: () => void): void {
|
||||
switch (failureReason) {
|
||||
case 'inferFailed':
|
||||
host.showErrorMessage(message, 'Install dependencies', 'Learn more').then(
|
||||
(str) => {
|
||||
switch (str) {
|
||||
case 'Learn more':
|
||||
host.showInformationMessage(`Add ${binName} directory to path, or set "mssql-bdc.${binName}-path" config to ${binName} binary.`);
|
||||
break;
|
||||
case 'Install dependencies':
|
||||
installDependencies();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
break;
|
||||
case 'configuredFileMissing':
|
||||
host.showErrorMessage(message, 'Install dependencies').then(
|
||||
(str) => {
|
||||
if (str === 'Install dependencies') {
|
||||
installDependencies();
|
||||
}
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkForBinary(context: BinCheckContext, bin: string | undefined, binName: string, inferFailedMessage: string, configuredFileMissingMessage: string, alertOnFail: boolean): Promise<boolean> {
|
||||
if (!bin) {
|
||||
const fb = await findBinary(context.shell, binName);
|
||||
|
||||
if (fb.err || fb.output.length === 0) {
|
||||
if (alertOnFail) {
|
||||
alertNoBin(context.host, binName, 'inferFailed', inferFailedMessage, context.installDependenciesCallback);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
context.binFound = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context.shell.isWindows) {
|
||||
context.binFound = context.fs.existsSync(bin);
|
||||
} else {
|
||||
const sr = await context.shell.exec(`ls ${bin}`);
|
||||
context.binFound = (!!sr && sr.code === 0);
|
||||
}
|
||||
if (context.binFound) {
|
||||
context.binPath = bin;
|
||||
} else {
|
||||
if (alertOnFail) {
|
||||
alertNoBin(context.host, binName, 'configuredFileMissing', configuredFileMissingMessage, context.installDependenciesCallback);
|
||||
}
|
||||
}
|
||||
|
||||
return context.binFound;
|
||||
}
|
||||
66
extensions/big-data-cluster/src/kubectl/compatibility.ts
Normal file
66
extensions/big-data-cluster/src/kubectl/compatibility.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Errorable, failed } from '../interfaces';
|
||||
|
||||
interface CompatibilityGuaranteed {
|
||||
readonly guaranteed: true;
|
||||
}
|
||||
|
||||
interface CompatibilityNotGuaranteed {
|
||||
readonly guaranteed: false;
|
||||
readonly didCheck: boolean;
|
||||
readonly clientVersion: string;
|
||||
readonly serverVersion: string;
|
||||
}
|
||||
|
||||
export type Compatibility = CompatibilityGuaranteed | CompatibilityNotGuaranteed;
|
||||
|
||||
export function isGuaranteedCompatible(c: Compatibility): c is CompatibilityGuaranteed {
|
||||
return c.guaranteed;
|
||||
}
|
||||
|
||||
export interface Version {
|
||||
readonly major: string;
|
||||
readonly minor: string;
|
||||
readonly gitVersion: string;
|
||||
}
|
||||
|
||||
export async function check(kubectlLoadJSON: (cmd: string) => Promise<Errorable<any>>): Promise<Compatibility> {
|
||||
const version = await kubectlLoadJSON('version -o json');
|
||||
if (failed(version)) {
|
||||
return {
|
||||
guaranteed: false,
|
||||
didCheck: false,
|
||||
clientVersion: '',
|
||||
serverVersion: ''
|
||||
};
|
||||
}
|
||||
|
||||
const clientVersion: Version = version.result.clientVersion;
|
||||
const serverVersion: Version = version.result.serverVersion;
|
||||
|
||||
if (isCompatible(clientVersion, serverVersion)) {
|
||||
return { guaranteed: true };
|
||||
}
|
||||
|
||||
return {
|
||||
guaranteed: false,
|
||||
didCheck: true,
|
||||
clientVersion: clientVersion.gitVersion,
|
||||
serverVersion: serverVersion.gitVersion
|
||||
};
|
||||
}
|
||||
|
||||
function isCompatible(clientVersion: Version, serverVersion: Version): boolean {
|
||||
if (clientVersion.major === serverVersion.major) {
|
||||
const clientMinor = Number.parseInt(clientVersion.minor);
|
||||
const serverMinor = Number.parseInt(serverVersion.minor);
|
||||
if (Number.isInteger(clientMinor) && Number.isInteger(serverMinor) && Math.abs(clientMinor - serverMinor) <= 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
42
extensions/big-data-cluster/src/kubectl/host.ts
Normal file
42
extensions/big-data-cluster/src/kubectl/host.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export interface Host {
|
||||
showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined>;
|
||||
showWarningMessage(message: string, ...items: string[]): Thenable<string | undefined>;
|
||||
showInformationMessage(message: string, ...items: string[]): Thenable<string | undefined>;
|
||||
getConfiguration(key: string): any;
|
||||
onDidChangeConfiguration(listener: (ch: vscode.ConfigurationChangeEvent) => any): vscode.Disposable;
|
||||
}
|
||||
|
||||
export const host: Host = {
|
||||
showErrorMessage : showErrorMessage,
|
||||
showWarningMessage : showWarningMessage,
|
||||
showInformationMessage : showInformationMessage,
|
||||
getConfiguration : getConfiguration,
|
||||
onDidChangeConfiguration : onDidChangeConfiguration,
|
||||
};
|
||||
|
||||
function showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||
return vscode.window.showErrorMessage(message, ...items);
|
||||
}
|
||||
|
||||
function showWarningMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||
return vscode.window.showWarningMessage(message, ...items);
|
||||
}
|
||||
|
||||
function showInformationMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||
return vscode.window.showInformationMessage(message, ...items);
|
||||
}
|
||||
|
||||
function getConfiguration(key: string): any {
|
||||
return vscode.workspace.getConfiguration(key);
|
||||
}
|
||||
|
||||
function onDidChangeConfiguration(listener: (e: vscode.ConfigurationChangeEvent) => any): vscode.Disposable {
|
||||
return vscode.workspace.onDidChangeConfiguration(listener);
|
||||
}
|
||||
26
extensions/big-data-cluster/src/kubectl/kubeChannel.ts
Normal file
26
extensions/big-data-cluster/src/kubectl/kubeChannel.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from "vscode";
|
||||
|
||||
export interface ISqlServerBigDataClusterChannel {
|
||||
showOutput(message: any, title?: string): void;
|
||||
}
|
||||
|
||||
class SqlServerBigDataCluster implements ISqlServerBigDataClusterChannel {
|
||||
private readonly channel: vscode.OutputChannel = vscode.window.createOutputChannel("SQL Server big data cluster");
|
||||
|
||||
showOutput(message: any, title?: string): void {
|
||||
if (title) {
|
||||
const simplifiedTime = (new Date()).toISOString().replace(/z|t/gi, ' ').trim(); // YYYY-MM-DD HH:mm:ss.sss
|
||||
const hightlightingTitle = `[${title} ${simplifiedTime}]`;
|
||||
this.channel.appendLine(hightlightingTitle);
|
||||
}
|
||||
this.channel.appendLine(message);
|
||||
this.channel.show();
|
||||
}
|
||||
}
|
||||
|
||||
export const sqlserverbigdataclusterchannel: ISqlServerBigDataClusterChannel = new SqlServerBigDataCluster();
|
||||
130
extensions/big-data-cluster/src/kubectl/kubectl.ts
Normal file
130
extensions/big-data-cluster/src/kubectl/kubectl.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Host } from './host';
|
||||
import { FS } from '../utility/fs';
|
||||
import { Shell, ShellResult } from '../utility/shell';
|
||||
import * as binutil from './binutil';
|
||||
import { Errorable } from '../interfaces';
|
||||
import * as compatibility from './compatibility';
|
||||
import { getToolPath } from '../config/config';
|
||||
|
||||
export interface Kubectl {
|
||||
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean>;
|
||||
asJson<T>(command: string): Promise<Errorable<T>>;
|
||||
}
|
||||
|
||||
interface Context {
|
||||
readonly host: Host;
|
||||
readonly fs: FS;
|
||||
readonly shell: Shell;
|
||||
readonly installDependenciesCallback: () => void;
|
||||
binFound: boolean;
|
||||
binPath: string;
|
||||
}
|
||||
|
||||
class KubectlImpl implements Kubectl {
|
||||
constructor(host: Host, fs: FS, shell: Shell, installDependenciesCallback: () => void, kubectlFound: boolean) {
|
||||
this.context = { host : host, fs : fs, shell : shell, installDependenciesCallback : installDependenciesCallback, binFound : kubectlFound, binPath : 'kubectl' };
|
||||
}
|
||||
|
||||
private readonly context: Context;
|
||||
|
||||
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
|
||||
return checkPresent(this.context, errorMessageMode);
|
||||
}
|
||||
asJson<T>(command: string): Promise<Errorable<T>> {
|
||||
return asJson(this.context, command);
|
||||
}
|
||||
}
|
||||
|
||||
export function create(host: Host, fs: FS, shell: Shell, installDependenciesCallback: () => void): Kubectl {
|
||||
return new KubectlImpl(host, fs, shell, installDependenciesCallback, false);
|
||||
}
|
||||
|
||||
export enum CheckPresentMessageMode {
|
||||
Command,
|
||||
Activation,
|
||||
Silent,
|
||||
}
|
||||
|
||||
async function checkPresent(context: Context, errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
|
||||
if (context.binFound) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return await checkForKubectlInternal(context, errorMessageMode);
|
||||
}
|
||||
|
||||
async function checkForKubectlInternal(context: Context, errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
|
||||
const binName = 'kubectl';
|
||||
const bin = getToolPath(context.host, context.shell, binName);
|
||||
|
||||
const contextMessage = getCheckKubectlContextMessage(errorMessageMode);
|
||||
const inferFailedMessage = `Could not find "${binName}" binary.${contextMessage}`;
|
||||
const configuredFileMissingMessage = `${bin} is not installed. ${contextMessage}`;
|
||||
|
||||
return await binutil.checkForBinary(context, bin, binName, inferFailedMessage, configuredFileMissingMessage, errorMessageMode !== CheckPresentMessageMode.Silent);
|
||||
}
|
||||
|
||||
function getCheckKubectlContextMessage(errorMessageMode: CheckPresentMessageMode): string {
|
||||
if (errorMessageMode === CheckPresentMessageMode.Activation) {
|
||||
return ' SQL Server Big data cluster requires kubernetes.';
|
||||
} else if (errorMessageMode === CheckPresentMessageMode.Command) {
|
||||
return ' Cannot execute command.';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
async function invokeAsync(context: Context, command: string, stdin?: string): Promise<ShellResult | undefined> {
|
||||
if (await checkPresent(context, CheckPresentMessageMode.Command)) {
|
||||
const bin = baseKubectlPath(context);
|
||||
const cmd = `${bin} ${command}`;
|
||||
const sr = await context.shell.exec(cmd, stdin);
|
||||
if (sr && sr.code !== 0) {
|
||||
checkPossibleIncompatibility(context);
|
||||
}
|
||||
return sr;
|
||||
} else {
|
||||
return { code: -1, stdout: '', stderr: '' };
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: invalidate this when the context changes or if we know kubectl has changed (e.g. config)
|
||||
let checkedCompatibility = false; // We don't want to spam the user (or CPU!) repeatedly running the version check
|
||||
|
||||
async function checkPossibleIncompatibility(context: Context): Promise<void> {
|
||||
if (checkedCompatibility) {
|
||||
return;
|
||||
}
|
||||
checkedCompatibility = true;
|
||||
const compat = await compatibility.check((cmd) => asJson<compatibility.Version>(context, cmd));
|
||||
if (!compatibility.isGuaranteedCompatible(compat) && compat.didCheck) {
|
||||
const versionAlert = `kubectl version ${compat.clientVersion} may be incompatible with cluster Kubernetes version ${compat.serverVersion}`;
|
||||
context.host.showWarningMessage(versionAlert);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function baseKubectlPath(context: Context): string {
|
||||
let bin = getToolPath(context.host, context.shell, 'kubectl');
|
||||
if (!bin) {
|
||||
bin = 'kubectl';
|
||||
}
|
||||
return bin;
|
||||
}
|
||||
|
||||
async function asJson<T>(context: Context, command: string): Promise<Errorable<T>> {
|
||||
const shellResult = await invokeAsync(context, command);
|
||||
if (!shellResult) {
|
||||
return { succeeded: false, error: [`Unable to run command (${command})`] };
|
||||
}
|
||||
|
||||
if (shellResult.code === 0) {
|
||||
return { succeeded: true, result: JSON.parse(shellResult.stdout.trim()) as T };
|
||||
|
||||
}
|
||||
return { succeeded: false, error: [ shellResult.stderr ] };
|
||||
}
|
||||
86
extensions/big-data-cluster/src/kubectl/kubectlUtils.ts
Normal file
86
extensions/big-data-cluster/src/kubectl/kubectlUtils.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from "vscode";
|
||||
import { Kubectl } from "./kubectl";
|
||||
import { failed } from "../interfaces";
|
||||
|
||||
export interface KubectlContext {
|
||||
readonly clusterName: string;
|
||||
readonly contextName: string;
|
||||
readonly userName: string;
|
||||
readonly active: boolean;
|
||||
}
|
||||
|
||||
interface Kubeconfig {
|
||||
readonly apiVersion: string;
|
||||
readonly 'current-context': string;
|
||||
readonly clusters: {
|
||||
readonly name: string;
|
||||
readonly cluster: {
|
||||
readonly server: string;
|
||||
readonly 'certificate-authority'?: string;
|
||||
readonly 'certificate-authority-data'?: string;
|
||||
};
|
||||
}[] | undefined;
|
||||
readonly contexts: {
|
||||
readonly name: string;
|
||||
readonly context: {
|
||||
readonly cluster: string;
|
||||
readonly user: string;
|
||||
readonly namespace?: string;
|
||||
};
|
||||
}[] | undefined;
|
||||
readonly users: {
|
||||
readonly name: string;
|
||||
readonly user: {};
|
||||
}[] | undefined;
|
||||
}
|
||||
|
||||
export interface ClusterConfig {
|
||||
readonly server: string;
|
||||
readonly certificateAuthority: string | undefined;
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function getKubeconfig(kubectl: Kubectl): Promise<Kubeconfig | null> {
|
||||
const shellResult = await kubectl.asJson<any>("config view -o json");
|
||||
if (failed(shellResult)) {
|
||||
vscode.window.showErrorMessage(shellResult.error[0]);
|
||||
return null;
|
||||
}
|
||||
return shellResult.result;
|
||||
}
|
||||
|
||||
export async function getCurrentClusterConfig(kubectl: Kubectl): Promise<ClusterConfig | undefined> {
|
||||
const kubeConfig = await getKubeconfig(kubectl);
|
||||
if (!kubeConfig || !kubeConfig.clusters || !kubeConfig.contexts) {
|
||||
return undefined;
|
||||
}
|
||||
const contextConfig = kubeConfig.contexts.find((context) => context.name === kubeConfig["current-context"])!;
|
||||
const clusterConfig = kubeConfig.clusters.find((cluster) => cluster.name === contextConfig.context.cluster)!;
|
||||
return {
|
||||
server: clusterConfig.cluster.server,
|
||||
certificateAuthority: clusterConfig.cluster["certificate-authority"]
|
||||
};
|
||||
}
|
||||
|
||||
export async function getContexts(kubectl: Kubectl): Promise<KubectlContext[]> {
|
||||
const kubectlConfig = await getKubeconfig(kubectl);
|
||||
if (!kubectlConfig) {
|
||||
return [];
|
||||
}
|
||||
const currentContext = kubectlConfig["current-context"];
|
||||
const contexts = kubectlConfig.contexts || [];
|
||||
return contexts.map((c) => {
|
||||
return {
|
||||
clusterName: c.context.cluster,
|
||||
contextName: c.name,
|
||||
userName: c.context.user,
|
||||
active: c.name === currentContext
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -6,10 +6,22 @@
|
||||
|
||||
import vscode = require('vscode');
|
||||
import { MainController } from './mainController';
|
||||
|
||||
import { fs } from './utility/fs';
|
||||
|
||||
import { host } from './kubectl/host';
|
||||
import { sqlserverbigdataclusterchannel } from './kubectl/kubeChannel';
|
||||
import { shell, Shell } from './utility/shell';
|
||||
import { CheckPresentMessageMode, create as kubectlCreate } from './kubectl/kubectl';
|
||||
import { installKubectl } from './installer/installer';
|
||||
import { Errorable, failed } from './interfaces';
|
||||
|
||||
const kubectl = kubectlCreate(host, fs, shell, installDependencies);
|
||||
export let controller: MainController;
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
controller = new MainController(context);
|
||||
kubectl.checkPresent(CheckPresentMessageMode.Activation);
|
||||
controller = new MainController(context, kubectl);
|
||||
controller.activate();
|
||||
}
|
||||
|
||||
@@ -19,3 +31,28 @@ export function deactivate(): void {
|
||||
controller.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
export async function installDependencies() {
|
||||
const gotKubectl = await kubectl.checkPresent(CheckPresentMessageMode.Silent);
|
||||
|
||||
|
||||
const installPromises = [
|
||||
installDependency("kubectl", gotKubectl, installKubectl),
|
||||
];
|
||||
|
||||
await Promise.all(installPromises);
|
||||
|
||||
sqlserverbigdataclusterchannel.showOutput("Done");
|
||||
}
|
||||
|
||||
async function installDependency(name: string, alreadyGot: boolean, installFunc: (shell: Shell) => Promise<Errorable<null>>): Promise<void> {
|
||||
if (alreadyGot) {
|
||||
sqlserverbigdataclusterchannel.showOutput(`Already got ${name}...`);
|
||||
} else {
|
||||
sqlserverbigdataclusterchannel.showOutput(`Installing ${name}...`);
|
||||
const result = await installFunc(shell);
|
||||
if (failed(result)) {
|
||||
sqlserverbigdataclusterchannel.showOutput(`Unable to install ${name}: ${result.error[0]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,17 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { CreateClusterWizard } from './wizards/create-cluster/createClusterWizard';
|
||||
|
||||
import { Kubectl } from './kubectl/kubectl';
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export class MainController {
|
||||
protected _context: vscode.ExtensionContext;
|
||||
protected _kubectl : Kubectl;
|
||||
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
public constructor(context: vscode.ExtensionContext, kubectl: Kubectl) {
|
||||
this._context = context;
|
||||
this._kubectl = kubectl;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,7 +24,7 @@ export class MainController {
|
||||
*/
|
||||
public activate(): void {
|
||||
vscode.commands.registerCommand('mssql.cluster.create', () => {
|
||||
let wizard = new CreateClusterWizard(this._context);
|
||||
let wizard = new CreateClusterWizard(this._context, this._kubectl);
|
||||
wizard.open();
|
||||
});
|
||||
}
|
||||
|
||||
65
extensions/big-data-cluster/src/utility/fs.ts
Normal file
65
extensions/big-data-cluster/src/utility/fs.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as sysfs from 'fs';
|
||||
|
||||
export interface FS {
|
||||
existsSync(path: string): boolean;
|
||||
readFile(filename: string, encoding: string, callback: (err: NodeJS.ErrnoException, data: string) => void): void;
|
||||
readFileSync(filename: string, encoding: string): string;
|
||||
readFileToBufferSync(filename: string): Buffer;
|
||||
writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void;
|
||||
writeFileSync(filename: string, data: any): void;
|
||||
dirSync(path: string): string[];
|
||||
unlinkAsync(path: string): Promise<void>;
|
||||
existsAsync(path: string): Promise<boolean>;
|
||||
openAsync(path: string, flags: string): Promise<void>;
|
||||
statSync(path: string): sysfs.Stats;
|
||||
}
|
||||
|
||||
export const fs: FS = {
|
||||
existsSync: (path) => sysfs.existsSync(path),
|
||||
readFile: (filename, encoding, callback) => sysfs.readFile(filename, encoding, callback),
|
||||
readFileSync: (filename, encoding) => sysfs.readFileSync(filename, encoding),
|
||||
readFileToBufferSync: (filename) => sysfs.readFileSync(filename),
|
||||
writeFile: (filename, data, callback) => sysfs.writeFile(filename, data, callback),
|
||||
writeFileSync: (filename, data) => sysfs.writeFileSync(filename, data),
|
||||
dirSync: (path) => sysfs.readdirSync(path),
|
||||
|
||||
unlinkAsync: (path) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
sysfs.unlink(path, (error) => {
|
||||
if (error) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
existsAsync: (path) => {
|
||||
return new Promise((resolve) => {
|
||||
sysfs.exists(path, (exists) => {
|
||||
resolve(exists);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
openAsync: (path, flags) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
sysfs.open(path, flags, (error, _fd) => {
|
||||
if (error) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
statSync: (path) => sysfs.statSync(path)
|
||||
};
|
||||
204
extensions/big-data-cluster/src/utility/shell.ts
Normal file
204
extensions/big-data-cluster/src/utility/shell.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import * as shelljs from 'shelljs';
|
||||
import * as path from 'path';
|
||||
import { getActiveKubeconfig, getToolPath } from '../config/config';
|
||||
import { host } from '../kubectl/host';
|
||||
|
||||
export enum Platform {
|
||||
Windows,
|
||||
MacOS,
|
||||
Linux,
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
export interface ExecCallback extends shelljs.ExecCallback {}
|
||||
|
||||
export interface Shell {
|
||||
isWindows(): boolean;
|
||||
isUnix(): boolean;
|
||||
platform(): Platform;
|
||||
home(): string;
|
||||
combinePath(basePath: string, relativePath: string): string;
|
||||
fileUri(filePath: string): vscode.Uri;
|
||||
execOpts(): any;
|
||||
exec(cmd: string, stdin?: string): Promise<ShellResult | undefined>;
|
||||
execCore(cmd: string, opts: any, stdin?: string): Promise<ShellResult>;
|
||||
unquotedPath(path: string): string;
|
||||
which(bin: string): string | null;
|
||||
cat(path: string): string;
|
||||
ls(path: string): string[];
|
||||
}
|
||||
|
||||
export const shell: Shell = {
|
||||
isWindows : isWindows,
|
||||
isUnix : isUnix,
|
||||
platform : platform,
|
||||
home : home,
|
||||
combinePath : combinePath,
|
||||
fileUri : fileUri,
|
||||
execOpts : execOpts,
|
||||
exec : exec,
|
||||
execCore : execCore,
|
||||
unquotedPath : unquotedPath,
|
||||
which: which,
|
||||
cat: cat,
|
||||
ls: ls,
|
||||
};
|
||||
|
||||
const WINDOWS: string = 'win32';
|
||||
|
||||
export interface ShellResult {
|
||||
readonly code: number;
|
||||
readonly stdout: string;
|
||||
readonly stderr: string;
|
||||
}
|
||||
|
||||
export type ShellHandler = (code: number, stdout: string, stderr: string) => void;
|
||||
|
||||
function isWindows(): boolean {
|
||||
return (process.platform === WINDOWS);
|
||||
}
|
||||
|
||||
function isUnix(): boolean {
|
||||
return !isWindows();
|
||||
}
|
||||
|
||||
function platform(): Platform {
|
||||
switch (process.platform) {
|
||||
case 'win32': return Platform.Windows;
|
||||
case 'darwin': return Platform.MacOS;
|
||||
case 'linux': return Platform.Linux;
|
||||
default: return Platform.Unsupported;
|
||||
}
|
||||
}
|
||||
|
||||
function concatIfBoth(s1: string | undefined, s2: string | undefined): string | undefined {
|
||||
return s1 && s2 ? s1.concat(s2) : undefined;
|
||||
}
|
||||
|
||||
function home(): string {
|
||||
return process.env['HOME'] ||
|
||||
concatIfBoth(process.env['HOMEDRIVE'], process.env['HOMEPATH']) ||
|
||||
process.env['USERPROFILE'] ||
|
||||
'';
|
||||
}
|
||||
|
||||
function combinePath(basePath: string, relativePath: string) {
|
||||
let separator = '/';
|
||||
if (isWindows()) {
|
||||
relativePath = relativePath.replace(/\//g, '\\');
|
||||
separator = '\\';
|
||||
}
|
||||
return basePath + separator + relativePath;
|
||||
}
|
||||
|
||||
function isWindowsFilePath(filePath: string) {
|
||||
return filePath[1] === ':' && filePath[2] === '\\';
|
||||
}
|
||||
|
||||
function fileUri(filePath: string): vscode.Uri {
|
||||
if (isWindowsFilePath(filePath)) {
|
||||
return vscode.Uri.parse('file:///' + filePath.replace(/\\/g, '/'));
|
||||
}
|
||||
return vscode.Uri.parse('file://' + filePath);
|
||||
}
|
||||
|
||||
function execOpts(): any {
|
||||
let env = process.env;
|
||||
if (isWindows()) {
|
||||
env = Object.assign({ }, env, { HOME: home() });
|
||||
}
|
||||
env = shellEnvironment(env);
|
||||
const opts = {
|
||||
cwd: vscode.workspace.rootPath,
|
||||
env: env,
|
||||
async: true
|
||||
};
|
||||
return opts;
|
||||
}
|
||||
|
||||
async function exec(cmd: string, stdin?: string): Promise<ShellResult | undefined> {
|
||||
try {
|
||||
return await execCore(cmd, execOpts(), stdin);
|
||||
} catch (ex) {
|
||||
vscode.window.showErrorMessage(ex);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function execCore(cmd: string, opts: any, stdin?: string): Promise<ShellResult> {
|
||||
return new Promise<ShellResult>((resolve) => {
|
||||
const proc = shelljs.exec(cmd, opts, (code, stdout, stderr) => resolve({code : code, stdout : stdout, stderr : stderr}));
|
||||
if (stdin) {
|
||||
proc.stdin.end(stdin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function unquotedPath(path: string): string {
|
||||
if (isWindows() && path && path.length > 1 && path.startsWith('"') && path.endsWith('"')) {
|
||||
return path.substring(1, path.length - 1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
export function shellEnvironment(baseEnvironment: any): any {
|
||||
const env = Object.assign({}, baseEnvironment);
|
||||
const pathVariable = pathVariableName(env);
|
||||
for (const tool of ['kubectl']) {
|
||||
const toolPath = getToolPath(host, shell, tool);
|
||||
if (toolPath) {
|
||||
const toolDirectory = path.dirname(toolPath);
|
||||
const currentPath = env[pathVariable];
|
||||
env[pathVariable] = toolDirectory + (currentPath ? `${pathEntrySeparator()}${currentPath}` : '');
|
||||
}
|
||||
}
|
||||
|
||||
const kubeconfig = getActiveKubeconfig();
|
||||
if (kubeconfig) {
|
||||
env['KUBECONFIG'] = kubeconfig;
|
||||
}
|
||||
|
||||
return env;
|
||||
}
|
||||
|
||||
function pathVariableName(env: any): string {
|
||||
if (isWindows()) {
|
||||
for (const v of Object.keys(env)) {
|
||||
if (v.toLowerCase() === "path") {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
return "PATH";
|
||||
}
|
||||
|
||||
function pathEntrySeparator() {
|
||||
return isWindows() ? ';' : ':';
|
||||
}
|
||||
|
||||
function which(bin: string): string | null {
|
||||
return shelljs.which(bin);
|
||||
}
|
||||
|
||||
function cat(path: string): string {
|
||||
return shelljs.cat(path);
|
||||
}
|
||||
|
||||
function ls(path: string): string[] {
|
||||
return shelljs.ls(path);
|
||||
}
|
||||
|
||||
export function shellMessage(sr: ShellResult | undefined, invocationFailureMessage: string): string {
|
||||
if (!sr) {
|
||||
return invocationFailureMessage;
|
||||
}
|
||||
return sr.code === 0 ? sr.stdout : sr.stderr;
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
import { IKubeConfigParser } from '../../data/kubeConfigParser';
|
||||
import { ClusterInfo, TargetClusterType, ClusterPorts, ContainerRegistryInfo, TargetClusterTypeInfo, ToolInfo } from '../../interfaces';
|
||||
import { getContexts, KubectlContext } from '../../kubectl/kubectlUtils';
|
||||
import { Kubectl } from '../../kubectl/kubectl';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
@@ -14,11 +16,11 @@ export class CreateClusterModel {
|
||||
|
||||
private _tmp_tools_installed: boolean = false;
|
||||
|
||||
constructor(private _kubeConfigParser: IKubeConfigParser) {
|
||||
constructor(private _kubectl : Kubectl) {
|
||||
}
|
||||
|
||||
public loadClusters(configPath: string): ClusterInfo[] {
|
||||
return this._kubeConfigParser.parse(configPath);
|
||||
public async loadClusters(): Promise<KubectlContext[]> {
|
||||
return await getContexts(this._kubectl);
|
||||
}
|
||||
|
||||
public getDefaultPorts(): Thenable<ClusterPorts> {
|
||||
@@ -107,7 +109,7 @@ export class CreateClusterModel {
|
||||
|
||||
public targetClusterType: TargetClusterType;
|
||||
|
||||
public selectedCluster: ClusterInfo;
|
||||
public selectedCluster: KubectlContext;
|
||||
|
||||
public adminUserName: string;
|
||||
|
||||
|
||||
@@ -9,18 +9,17 @@ import { SelectExistingClusterPage } from './pages/selectExistingClusterPage';
|
||||
import { SummaryPage } from './pages/summaryPage';
|
||||
import { SettingsPage } from './pages/settingsPage';
|
||||
import { ClusterProfilePage } from './pages/clusterProfilePage';
|
||||
import { TestKubeConfigParser } from '../../data/kubeConfigParser';
|
||||
import { ExtensionContext } from 'vscode';
|
||||
import { WizardBase } from '../wizardBase';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Kubectl } from '../../kubectl/kubectl';
|
||||
import { SelectTargetClusterTypePage } from './pages/selectTargetClusterTypePage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class CreateClusterWizard extends WizardBase<CreateClusterModel, CreateClusterWizard> {
|
||||
constructor(context: ExtensionContext) {
|
||||
let configParser = new TestKubeConfigParser();
|
||||
let model = new CreateClusterModel(configParser);
|
||||
constructor(context: ExtensionContext, kubectl: Kubectl) {
|
||||
let model = new CreateClusterModel(kubectl);
|
||||
super(model, context, localize('bdc-create.wizardTitle', 'Create a big data cluster'));
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import * as os from 'os';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import { CreateClusterWizard } from '../createClusterWizard';
|
||||
import { TargetClusterType } from '../../../interfaces';
|
||||
import { setActiveKubeconfig } from '../../../config/config';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
@@ -101,7 +103,7 @@ export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizar
|
||||
defaultUri: vscode.Uri.file(os.homedir()),
|
||||
openLabel: localize('bdc-selectKubeConfigFileText', 'Select'),
|
||||
filters: {
|
||||
'KubeConfig Files': ['kubeconfig'],
|
||||
'KubeConfig Files': ['*'],
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -114,8 +116,9 @@ export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizar
|
||||
let fileUri = fileUris[0];
|
||||
|
||||
configFileInput.value = fileUri.fsPath;
|
||||
await setActiveKubeconfig(fileUri.fsPath);
|
||||
|
||||
let clusters = self.wizard.model.loadClusters(fileUri.fsPath);
|
||||
let clusters = await self.wizard.model.loadClusters();
|
||||
|
||||
self.cards = [];
|
||||
if (clusters.length !== 0) {
|
||||
@@ -124,8 +127,8 @@ export class SelectExistingClusterPage extends WizardPageBase<CreateClusterWizar
|
||||
let cluster = clusters[i];
|
||||
let card = view.modelBuilder.card().withProperties({
|
||||
selected: i === 0,
|
||||
label: cluster.name,
|
||||
descriptions: [cluster.displayName, cluster.user],
|
||||
label: cluster.clusterName,
|
||||
descriptions: [cluster.clusterName, cluster.userName],
|
||||
cardType: sqlops.CardType.ListItem,
|
||||
iconPath: {
|
||||
dark: self.wizard.context.asAbsolutePath('images/cluster_inverse.svg'),
|
||||
|
||||
Reference in New Issue
Block a user