mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Ron/bdc script (#4221)
* WIP adding scripting support. * Adding deploy command along with additional env vars needed. * Adding script generation that sets envars, kube context, and mssqlctl * Adding test email for docker email envar until we update UI. * Adding cluster platform detection and disabling generate script after first click. * Fix spacing and adding comment.
This commit is contained in:
@@ -69,3 +69,11 @@ export enum ToolInstallationStatus {
|
|||||||
NotInstalled,
|
NotInstalled,
|
||||||
Installing
|
Installing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum ClusterType {
|
||||||
|
Unknown = 0,
|
||||||
|
AKS,
|
||||||
|
Minikube,
|
||||||
|
Kubernetes,
|
||||||
|
Other
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ import { getToolPath } from '../config/config';
|
|||||||
export interface Kubectl {
|
export interface Kubectl {
|
||||||
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean>;
|
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean>;
|
||||||
asJson<T>(command: string): Promise<Errorable<T>>;
|
asJson<T>(command: string): Promise<Errorable<T>>;
|
||||||
|
invokeAsync(command: string, stdin?: string): Promise<ShellResult | undefined>;
|
||||||
|
getContext(): Context;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Context {
|
interface Context {
|
||||||
@@ -30,7 +32,7 @@ class KubectlImpl implements Kubectl {
|
|||||||
this.context = { host : host, fs : fs, shell : shell, installDependenciesCallback : installDependenciesCallback, binFound : kubectlFound, binPath : 'kubectl' };
|
this.context = { host : host, fs : fs, shell : shell, installDependenciesCallback : installDependenciesCallback, binFound : kubectlFound, binPath : 'kubectl' };
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly context: Context;
|
readonly context: Context;
|
||||||
|
|
||||||
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
|
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
|
||||||
return checkPresent(this.context, errorMessageMode);
|
return checkPresent(this.context, errorMessageMode);
|
||||||
@@ -38,6 +40,13 @@ class KubectlImpl implements Kubectl {
|
|||||||
asJson<T>(command: string): Promise<Errorable<T>> {
|
asJson<T>(command: string): Promise<Errorable<T>> {
|
||||||
return asJson(this.context, command);
|
return asJson(this.context, command);
|
||||||
}
|
}
|
||||||
|
invokeAsync(command: string, stdin?: string): Promise<ShellResult | undefined> {
|
||||||
|
return invokeAsync(this.context, command, stdin);
|
||||||
|
}
|
||||||
|
|
||||||
|
getContext(): Context {
|
||||||
|
return this.context;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function create(host: Host, fs: FS, shell: Shell, installDependenciesCallback: () => void): Kubectl {
|
export function create(host: Host, fs: FS, shell: Shell, installDependenciesCallback: () => void): Kubectl {
|
||||||
@@ -108,7 +117,7 @@ async function checkPossibleIncompatibility(context: Context): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function baseKubectlPath(context: Context): string {
|
export function baseKubectlPath(context: Context): string {
|
||||||
let bin = getToolPath(context.host, context.shell, 'kubectl');
|
let bin = getToolPath(context.host, context.shell, 'kubectl');
|
||||||
if (!bin) {
|
if (!bin) {
|
||||||
bin = 'kubectl';
|
bin = 'kubectl';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import { Kubectl } from "./kubectl";
|
import { Kubectl } from "./kubectl";
|
||||||
import { failed } from "../interfaces";
|
import { failed, ClusterType } from "../interfaces";
|
||||||
|
|
||||||
export interface KubectlContext {
|
export interface KubectlContext {
|
||||||
readonly clusterName: string;
|
readonly clusterName: string;
|
||||||
@@ -83,4 +83,50 @@ export async function getContexts(kubectl: Kubectl): Promise<KubectlContext[]> {
|
|||||||
active: c.name === currentContext
|
active: c.name === currentContext
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setContext(kubectl: Kubectl, targetContext: string): Promise<void> {
|
||||||
|
const shellResult = await kubectl.invokeAsync(`config use-context ${targetContext}`);
|
||||||
|
if (!shellResult || shellResult.code != 0) {
|
||||||
|
// TODO: Update error handling for now.
|
||||||
|
vscode.window.showErrorMessage(`Failed to set '${targetContext}' as current cluster: ${shellResult ? shellResult.stderr : "Unable to run kubectl"}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function inferCurrentClusterType(kubectl: Kubectl): Promise<ClusterType> {
|
||||||
|
let latestContextName = "";
|
||||||
|
|
||||||
|
const ctxsr = await kubectl.invokeAsync('config current-context');
|
||||||
|
if (ctxsr && ctxsr.code === 0) {
|
||||||
|
latestContextName = ctxsr.stdout.trim();
|
||||||
|
} else {
|
||||||
|
return ClusterType.Other;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cisr = await kubectl.invokeAsync('cluster-info');
|
||||||
|
if (!cisr || cisr.code !== 0) {
|
||||||
|
return ClusterType.Unknown;
|
||||||
|
}
|
||||||
|
const masterInfos = cisr.stdout.split('\n')
|
||||||
|
.filter((s) => s.indexOf('master is running at') >= 0);
|
||||||
|
|
||||||
|
if (masterInfos.length === 0) {
|
||||||
|
return ClusterType.Other;
|
||||||
|
}
|
||||||
|
|
||||||
|
const masterInfo = masterInfos[0];
|
||||||
|
if (masterInfo.indexOf('azmk8s.io') >= 0 || masterInfo.indexOf('azure.com') >= 0) {
|
||||||
|
return ClusterType.AKS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestContextName) {
|
||||||
|
const gcsr = await kubectl.invokeAsync(`config get-contexts ${latestContextName}`);
|
||||||
|
if (gcsr && gcsr.code === 0) {
|
||||||
|
if (gcsr.stdout.indexOf('minikube') >= 0) {
|
||||||
|
return ClusterType.Minikube;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ClusterType.Other;
|
||||||
}
|
}
|
||||||
@@ -10,7 +10,7 @@ import { MainController } from './mainController';
|
|||||||
import { fs } from './utility/fs';
|
import { fs } from './utility/fs';
|
||||||
|
|
||||||
import { host } from './kubectl/host';
|
import { host } from './kubectl/host';
|
||||||
import { sqlserverbigdataclusterchannel } from './kubectl/kubeChannel';
|
import { sqlserverbigdataclusterchannel } from './kubectl/SqlServerBigDataClusterChannel';
|
||||||
import { shell, Shell } from './utility/shell';
|
import { shell, Shell } from './utility/shell';
|
||||||
import { CheckPresentMessageMode, create as kubectlCreate } from './kubectl/kubectl';
|
import { CheckPresentMessageMode, create as kubectlCreate } from './kubectl/kubectl';
|
||||||
import { installKubectl } from './installer/installer';
|
import { installKubectl } from './installer/installer';
|
||||||
|
|||||||
72
extensions/big-data-cluster/src/scripting/scripting.ts
Normal file
72
extensions/big-data-cluster/src/scripting/scripting.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { fs } from '../utility/fs';
|
||||||
|
import { Shell } from '../utility/shell';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as path from 'path';
|
||||||
|
import mkdirp = require('mkdirp');
|
||||||
|
import { Kubectl, baseKubectlPath } from '../kubectl/kubectl';
|
||||||
|
import { KubectlContext } from '../kubectl/kubectlUtils';
|
||||||
|
|
||||||
|
export interface Scriptable {
|
||||||
|
getScriptProperties(): Promise<ScriptingDictionary<string>>;
|
||||||
|
getTargetKubectlContext() : KubectlContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScriptingDictionary<V> {
|
||||||
|
[name: string]: V;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deployFilePrefix : string = 'mssql-bdc-deploy';
|
||||||
|
export class ScriptGenerator {
|
||||||
|
|
||||||
|
private _shell: Shell;
|
||||||
|
private _kubectl: Kubectl;
|
||||||
|
|
||||||
|
private _kubectlPath: string;
|
||||||
|
constructor(_kubectl: Kubectl) {
|
||||||
|
this._kubectl = _kubectl;
|
||||||
|
this._shell = this._kubectl.getContext().shell;
|
||||||
|
this._kubectlPath = baseKubectlPath(this._kubectl.getContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async generateDeploymentScript(scriptable: Scriptable) : Promise<void> {
|
||||||
|
let targetClusterName = scriptable.getTargetKubectlContext().clusterName;
|
||||||
|
let targetContextName = scriptable.getTargetKubectlContext().contextName;
|
||||||
|
|
||||||
|
let timestamp = new Date().getTime();
|
||||||
|
let deployFolder = this.getDeploymentFolder(this._shell);
|
||||||
|
let deployFileSuffix = this._shell.isWindows() ? `.bat` : `.sh`;
|
||||||
|
let deployFileName = `${deployFilePrefix}-${targetClusterName}-${timestamp}${deployFileSuffix}`;
|
||||||
|
let deployFilePath = path.join(deployFolder, deployFileName);
|
||||||
|
|
||||||
|
let envVars = "";
|
||||||
|
let propertiesDict = await scriptable.getScriptProperties();
|
||||||
|
for (let key in propertiesDict) {
|
||||||
|
let value = propertiesDict[key];
|
||||||
|
envVars += this._shell.isWindows() ? `Set ${key} = ${value}\n` : `export ${key} = ${value}\n`;
|
||||||
|
}
|
||||||
|
envVars += '\n';
|
||||||
|
|
||||||
|
let kubeContextcommand = `${this._kubectlPath} config use-context ${targetContextName}\n`;
|
||||||
|
// Todo: The API for mssqlctl may change per version, so need a version check to use proper syntax.
|
||||||
|
let deployCommand = `mssqlctl create cluster ${targetClusterName}\n`;
|
||||||
|
|
||||||
|
let deployContent = envVars + kubeContextcommand + deployCommand;
|
||||||
|
|
||||||
|
mkdirp.sync(deployFolder);
|
||||||
|
await fs.writeFile(deployFilePath, deployContent, handleError);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDeploymentFolder(shell: Shell): string {
|
||||||
|
return path.join(shell.home(), `.mssql-bdc/deployment`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleError = (err: NodeJS.ErrnoException) => {
|
||||||
|
if (err) {
|
||||||
|
vscode.window.showErrorMessage(err.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -5,23 +5,29 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { TargetClusterType, ClusterPorts, ContainerRegistryInfo, TargetClusterTypeInfo, ToolInfo, ToolInstallationStatus } from '../../interfaces';
|
import { TargetClusterType, ClusterPorts, ContainerRegistryInfo, TargetClusterTypeInfo, ToolInfo, ToolInstallationStatus } from '../../interfaces';
|
||||||
import { getContexts, KubectlContext } from '../../kubectl/kubectlUtils';
|
import { getContexts, KubectlContext, setContext, inferCurrentClusterType } from '../../kubectl/kubectlUtils';
|
||||||
import { Kubectl } from '../../kubectl/kubectl';
|
import { Kubectl } from '../../kubectl/kubectl';
|
||||||
|
import { Scriptable, ScriptingDictionary } from '../../scripting/scripting';
|
||||||
|
import { ClusterType} from '../../interfaces';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
export class CreateClusterModel {
|
export class CreateClusterModel implements Scriptable {
|
||||||
|
|
||||||
private _tmp_tools_installed: boolean = false;
|
private _tmp_tools_installed: boolean = false;
|
||||||
|
private scriptingProperties : ScriptingDictionary<string> = {};
|
||||||
constructor(private _kubectl: Kubectl) {
|
constructor(private _kubectl : Kubectl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public async loadClusters(): Promise<KubectlContext[]> {
|
public async loadClusters(): Promise<KubectlContext[]> {
|
||||||
return await getContexts(this._kubectl);
|
return await getContexts(this._kubectl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async changeKubernetesContext(targetContext: string): Promise<void> {
|
||||||
|
await setContext(this._kubectl, targetContext)
|
||||||
|
}
|
||||||
|
|
||||||
public getDefaultPorts(): Thenable<ClusterPorts> {
|
public getDefaultPorts(): Thenable<ClusterPorts> {
|
||||||
let promise = new Promise<ClusterPorts>(resolve => {
|
let promise = new Promise<ClusterPorts>(resolve => {
|
||||||
resolve({
|
resolve({
|
||||||
@@ -135,4 +141,56 @@ export class CreateClusterModel {
|
|||||||
public containerRegistryUserName: string;
|
public containerRegistryUserName: string;
|
||||||
|
|
||||||
public containerRegistryPassword: string;
|
public containerRegistryPassword: string;
|
||||||
|
|
||||||
|
public async getTargetClusterPlatform(targetContextName : string) : Promise<string> {
|
||||||
|
await setContext(this._kubectl, targetContextName);
|
||||||
|
let clusterType = await inferCurrentClusterType(this._kubectl);
|
||||||
|
|
||||||
|
switch (clusterType) {
|
||||||
|
case ClusterType.AKS:
|
||||||
|
return 'aks';
|
||||||
|
case ClusterType.Minikube:
|
||||||
|
return 'minikube';
|
||||||
|
case ClusterType.Other:
|
||||||
|
default:
|
||||||
|
return 'kubernetes';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getScriptProperties() : Promise<ScriptingDictionary<string>> {
|
||||||
|
|
||||||
|
// Cluster settings
|
||||||
|
this.scriptingProperties['CLUSTER_NAME'] = this.selectedCluster.clusterName;
|
||||||
|
this.scriptingProperties['CLUSTER_PLATFORM'] = await this.getTargetClusterPlatform(this.selectedCluster.contextName);
|
||||||
|
|
||||||
|
// Default pool count for now. TODO: Update from user input
|
||||||
|
this.scriptingProperties['CLUSTER_DATA_POOL_REPLICAS'] = '1';
|
||||||
|
this.scriptingProperties['CLUSTER_COMPUTE_POOL_REPLICAS'] = '2';
|
||||||
|
this.scriptingProperties['CLUSTER_STORAGE_POOL_REPLICAS'] = '3';
|
||||||
|
|
||||||
|
// SQL Server settings
|
||||||
|
this.scriptingProperties['CONTROLLER_USERNAME'] = this.adminUserName;
|
||||||
|
this.scriptingProperties['CONTROLLER_PASSWORD'] = this.adminPassword;
|
||||||
|
this.scriptingProperties['KNOX_PASSWORD'] = this.adminPassword;
|
||||||
|
this.scriptingProperties['MSSQL_SA_PASSWORD'] = this.adminPassword;
|
||||||
|
|
||||||
|
// docker settings
|
||||||
|
this.scriptingProperties['DOCKER_REPOSITORY'] = this.containerRepository;
|
||||||
|
this.scriptingProperties['DOCKER_REGISTRY' ] = this.containerRegistry;
|
||||||
|
this.scriptingProperties['DOCKER_PASSWORD'] = this.containerRegistryPassword;
|
||||||
|
this.scriptingProperties['DOCKER_USERNAME'] = this.containerRegistryUserName;
|
||||||
|
this.scriptingProperties['DOCKER_IMAGE_TAG'] = this.containerImageTag;
|
||||||
|
|
||||||
|
// port settings
|
||||||
|
this.scriptingProperties['MASTER_SQL_PORT'] = this.sqlPort;
|
||||||
|
this.scriptingProperties['KNOX_PORT'] = this.knoxPort;
|
||||||
|
this.scriptingProperties['GRAFANA_PORT'] = this.grafanaPort;
|
||||||
|
this.scriptingProperties['KIBANA_PORT'] = this.kibanaPort;
|
||||||
|
|
||||||
|
return this.scriptingProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTargetKubectlContext() : KubectlContext {
|
||||||
|
return this.selectedCluster;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,13 +14,15 @@ import { WizardBase } from '../wizardBase';
|
|||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import { Kubectl } from '../../kubectl/kubectl';
|
import { Kubectl } from '../../kubectl/kubectl';
|
||||||
import { SelectTargetClusterTypePage } from './pages/selectTargetClusterTypePage';
|
import { SelectTargetClusterTypePage } from './pages/selectTargetClusterTypePage';
|
||||||
|
import { ScriptGenerator } from '../../scripting/scripting';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
export class CreateClusterWizard extends WizardBase<CreateClusterModel, CreateClusterWizard> {
|
export class CreateClusterWizard extends WizardBase<CreateClusterModel, CreateClusterWizard> {
|
||||||
|
private scripter : ScriptGenerator;
|
||||||
constructor(context: ExtensionContext, kubectl: Kubectl) {
|
constructor(context: ExtensionContext, kubectl: Kubectl) {
|
||||||
let model = new CreateClusterModel(kubectl);
|
let model = new CreateClusterModel(kubectl);
|
||||||
super(model, context, localize('bdc-create.wizardTitle', 'Create a big data cluster'));
|
super(model, context, localize('bdc-create.wizardTitle', 'Create a big data cluster'));
|
||||||
|
this.scripter = new ScriptGenerator(kubectl);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(): void {
|
protected initialize(): void {
|
||||||
@@ -32,10 +34,16 @@ export class CreateClusterWizard extends WizardBase<CreateClusterModel, CreateCl
|
|||||||
this.setPages([targetClusterTypePage, settingsPage, clusterProfilePage, selectTargetClusterPage, summaryPage]);
|
this.setPages([targetClusterTypePage, settingsPage, clusterProfilePage, selectTargetClusterPage, summaryPage]);
|
||||||
|
|
||||||
this.wizardObject.generateScriptButton.label = localize('bdc-create.generateScriptsButtonText', 'Generate Scripts');
|
this.wizardObject.generateScriptButton.label = localize('bdc-create.generateScriptsButtonText', 'Generate Scripts');
|
||||||
this.wizardObject.generateScriptButton.hidden = true;
|
this.wizardObject.generateScriptButton.hidden = false;
|
||||||
this.wizardObject.doneButton.label = localize('bdc-create.createClusterButtonText', 'Create');
|
this.wizardObject.doneButton.label = localize('bdc-create.createClusterButtonText', 'Create');
|
||||||
|
|
||||||
this.wizardObject.generateScriptButton.onClick(() => { });
|
this.wizardObject.generateScriptButton.onClick(async () => {
|
||||||
|
this.wizardObject.generateScriptButton.enabled = false;
|
||||||
|
this.scripter.generateDeploymentScript(this.model).then( () => {
|
||||||
|
this.wizardObject.generateScriptButton.enabled = true;
|
||||||
|
//TODO: Add error handling.
|
||||||
|
});
|
||||||
|
});
|
||||||
this.wizardObject.doneButton.onClick(() => { });
|
this.wizardObject.doneButton.onClick(() => { });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user