mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-14 01:25:37 -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,
|
||||
Installing
|
||||
}
|
||||
|
||||
export enum ClusterType {
|
||||
Unknown = 0,
|
||||
AKS,
|
||||
Minikube,
|
||||
Kubernetes,
|
||||
Other
|
||||
}
|
||||
@@ -14,6 +14,8 @@ import { getToolPath } from '../config/config';
|
||||
export interface Kubectl {
|
||||
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean>;
|
||||
asJson<T>(command: string): Promise<Errorable<T>>;
|
||||
invokeAsync(command: string, stdin?: string): Promise<ShellResult | undefined>;
|
||||
getContext(): 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' };
|
||||
}
|
||||
|
||||
private readonly context: Context;
|
||||
readonly context: Context;
|
||||
|
||||
checkPresent(errorMessageMode: CheckPresentMessageMode): Promise<boolean> {
|
||||
return checkPresent(this.context, errorMessageMode);
|
||||
@@ -38,6 +40,13 @@ class KubectlImpl implements Kubectl {
|
||||
asJson<T>(command: string): Promise<Errorable<T>> {
|
||||
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 {
|
||||
@@ -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');
|
||||
if (!bin) {
|
||||
bin = 'kubectl';
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as vscode from "vscode";
|
||||
import { Kubectl } from "./kubectl";
|
||||
import { failed } from "../interfaces";
|
||||
import { failed, ClusterType } from "../interfaces";
|
||||
|
||||
export interface KubectlContext {
|
||||
readonly clusterName: string;
|
||||
@@ -83,4 +83,50 @@ export async function getContexts(kubectl: Kubectl): Promise<KubectlContext[]> {
|
||||
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 { host } from './kubectl/host';
|
||||
import { sqlserverbigdataclusterchannel } from './kubectl/kubeChannel';
|
||||
import { sqlserverbigdataclusterchannel } from './kubectl/SqlServerBigDataClusterChannel';
|
||||
import { shell, Shell } from './utility/shell';
|
||||
import { CheckPresentMessageMode, create as kubectlCreate } from './kubectl/kubectl';
|
||||
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';
|
||||
|
||||
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 { Scriptable, ScriptingDictionary } from '../../scripting/scripting';
|
||||
import { ClusterType} from '../../interfaces';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class CreateClusterModel {
|
||||
export class CreateClusterModel implements Scriptable {
|
||||
|
||||
private _tmp_tools_installed: boolean = false;
|
||||
|
||||
constructor(private _kubectl: Kubectl) {
|
||||
private scriptingProperties : ScriptingDictionary<string> = {};
|
||||
constructor(private _kubectl : Kubectl) {
|
||||
}
|
||||
|
||||
public async loadClusters(): Promise<KubectlContext[]> {
|
||||
return await getContexts(this._kubectl);
|
||||
}
|
||||
|
||||
public async changeKubernetesContext(targetContext: string): Promise<void> {
|
||||
await setContext(this._kubectl, targetContext)
|
||||
}
|
||||
|
||||
public getDefaultPorts(): Thenable<ClusterPorts> {
|
||||
let promise = new Promise<ClusterPorts>(resolve => {
|
||||
resolve({
|
||||
@@ -135,4 +141,56 @@ export class CreateClusterModel {
|
||||
public containerRegistryUserName: 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 { Kubectl } from '../../kubectl/kubectl';
|
||||
import { SelectTargetClusterTypePage } from './pages/selectTargetClusterTypePage';
|
||||
|
||||
import { ScriptGenerator } from '../../scripting/scripting';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class CreateClusterWizard extends WizardBase<CreateClusterModel, CreateClusterWizard> {
|
||||
private scripter : ScriptGenerator;
|
||||
constructor(context: ExtensionContext, kubectl: Kubectl) {
|
||||
let model = new CreateClusterModel(kubectl);
|
||||
super(model, context, localize('bdc-create.wizardTitle', 'Create a big data cluster'));
|
||||
this.scripter = new ScriptGenerator(kubectl);
|
||||
}
|
||||
|
||||
protected initialize(): void {
|
||||
@@ -32,10 +34,16 @@ export class CreateClusterWizard extends WizardBase<CreateClusterModel, CreateCl
|
||||
this.setPages([targetClusterTypePage, settingsPage, clusterProfilePage, selectTargetClusterPage, summaryPage]);
|
||||
|
||||
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.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(() => { });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user