mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Add support for low-privileged user to run spark notebooks (#12883)
* Add support for low-privileged user to run spark notebooks * error * fix test
This commit is contained in:
4
extensions/big-data-cluster/src/bdc.d.ts
vendored
4
extensions/big-data-cluster/src/bdc.d.ts
vendored
@@ -31,7 +31,9 @@ declare module 'bdc' {
|
|||||||
|
|
||||||
export interface IClusterController {
|
export interface IClusterController {
|
||||||
getClusterConfig(): Promise<any>;
|
getClusterConfig(): Promise<any>;
|
||||||
getKnoxUsername(clusterUsername: string): Promise<string>;
|
getKnoxUsername(defaultUsername: string): Promise<string>;
|
||||||
getEndPoints(promptConnect?: boolean): Promise<IEndPointsResponse>
|
getEndPoints(promptConnect?: boolean): Promise<IEndPointsResponse>
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,14 +173,14 @@ export class ClusterController implements IClusterController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getKnoxUsername(sqlLogin: string): Promise<string> {
|
public async getKnoxUsername(defaultUsername: string): Promise<string> {
|
||||||
// This all is necessary because prior to CU5 BDC deployments all had the same default username for
|
// This all is necessary because prior to CU5 BDC deployments all had the same default username for
|
||||||
// accessing the Knox gateway. But in the allowRunAsRoot setting was added and defaulted to false - so
|
// accessing the Knox gateway. But in the allowRunAsRoot setting was added and defaulted to false - so
|
||||||
// if that exists and is false then we use the username instead.
|
// if that exists and is false then we use the username instead.
|
||||||
// Note that the SQL username may not necessarily be correct here either - but currently this is what
|
// Note that the SQL username may not necessarily be correct here either - but currently this is what
|
||||||
// we're requiring to run Notebooks in a BDC
|
// we're requiring to run Notebooks in a BDC
|
||||||
const config = await this.getClusterConfig();
|
const config = await this.getClusterConfig();
|
||||||
return config.spec?.spec?.security?.allowRunAsRoot === false ? sqlLogin : DEFAULT_KNOX_USERNAME;
|
return config.spec?.spec?.security?.allowRunAsRoot === false ? defaultUsername : DEFAULT_KNOX_USERNAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getClusterConfig(promptConnect: boolean = false): Promise<any> {
|
public async getClusterConfig(promptConnect: boolean = false): Promise<any> {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as vscode from 'vscode';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
import * as constants from '../common/constants';
|
import * as constants from '../common/constants';
|
||||||
import { IPrompter, IQuestion, confirm } from '../prompts/question';
|
import { IPrompter, IQuestion, QuestionTypes } from '../prompts/question';
|
||||||
import CodeAdapter from '../prompts/adapter';
|
import CodeAdapter from '../prompts/adapter';
|
||||||
import { BookTreeItem, BookTreeItemType } from './bookTreeItem';
|
import { BookTreeItem, BookTreeItemType } from './bookTreeItem';
|
||||||
import { BookModel, BookVersion } from './bookModel';
|
import { BookModel, BookVersion } from './bookModel';
|
||||||
@@ -580,7 +580,7 @@ export class BookTreeViewProvider implements vscode.TreeDataProvider<BookTreeIte
|
|||||||
//Confirmation message dialog
|
//Confirmation message dialog
|
||||||
private async confirmReplace(): Promise<boolean> {
|
private async confirmReplace(): Promise<boolean> {
|
||||||
return await this.prompter.promptSingle<boolean>(<IQuestion>{
|
return await this.prompter.promptSingle<boolean>(<IQuestion>{
|
||||||
type: confirm,
|
type: QuestionTypes.confirm,
|
||||||
message: loc.confirmReplace,
|
message: loc.confirmReplace,
|
||||||
default: false
|
default: false
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -81,3 +81,12 @@ export const pythonMacInstallUrl = 'https://go.microsoft.com/fwlink/?linkid=2128
|
|||||||
export const pythonLinuxInstallUrl = 'https://go.microsoft.com/fwlink/?linkid=2110524';
|
export const pythonLinuxInstallUrl = 'https://go.microsoft.com/fwlink/?linkid=2110524';
|
||||||
|
|
||||||
export const notebookLanguages = ['notebook', 'ipynb'];
|
export const notebookLanguages = ['notebook', 'ipynb'];
|
||||||
|
|
||||||
|
export const KNOX_ENDPOINT_SERVER = 'host';
|
||||||
|
export const KNOX_ENDPOINT_PORT = 'knoxport';
|
||||||
|
export const KNOX_ENDPOINT_GATEWAY = 'gateway';
|
||||||
|
export const CONTROLLER_ENDPOINT = 'controller';
|
||||||
|
export const SQL_PROVIDER = 'MSSQL';
|
||||||
|
export const USER = 'user';
|
||||||
|
export const AUTHTYPE = 'authenticationType';
|
||||||
|
export const INTEGRATED_AUTH = 'integrated';
|
||||||
|
|||||||
14
extensions/notebook/src/common/extensionContextHelper.ts
Normal file
14
extensions/notebook/src/common/extensionContextHelper.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 class ExtensionContextHelper {
|
||||||
|
public static extensionContext: vscode.ExtensionContext;
|
||||||
|
|
||||||
|
public static setExtensionContext(extensionContext: vscode.ExtensionContext) {
|
||||||
|
ExtensionContextHelper.extensionContext = extensionContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,13 +3,14 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as bdc from 'bdc';
|
||||||
import * as childProcess from 'child_process';
|
import * as childProcess from 'child_process';
|
||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { notebookLanguages, notebookConfigKey, pinnedBooksConfigKey } from './constants';
|
import { notebookLanguages, notebookConfigKey, pinnedBooksConfigKey, AUTHTYPE, INTEGRATED_AUTH, KNOX_ENDPOINT_PORT, KNOX_ENDPOINT_SERVER } from './constants';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -114,13 +115,6 @@ interface RawEndpoint {
|
|||||||
port?: number;
|
port?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IEndpoint {
|
|
||||||
serviceName: string;
|
|
||||||
description: string;
|
|
||||||
endpoint: string;
|
|
||||||
protocol: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getOSPlatformId(): string {
|
export function getOSPlatformId(): string {
|
||||||
let platformId = undefined;
|
let platformId = undefined;
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
@@ -193,15 +187,15 @@ export function isEditorTitleFree(title: string): boolean {
|
|||||||
return !hasTextDoc && !hasNotebookDoc;
|
return !hasTextDoc && !hasNotebookDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getClusterEndpoints(serverInfo: azdata.ServerInfo): IEndpoint[] {
|
export function getClusterEndpoints(serverInfo: azdata.ServerInfo): bdc.IEndpointModel[] {
|
||||||
let endpoints: RawEndpoint[] = serverInfo.options['clusterEndpoints'];
|
let endpoints: RawEndpoint[] = serverInfo.options['clusterEndpoints'];
|
||||||
if (!endpoints || endpoints.length === 0) { return []; }
|
if (!endpoints || endpoints.length === 0) { return []; }
|
||||||
|
|
||||||
return endpoints.map(e => {
|
return endpoints.map(e => {
|
||||||
// If endpoint is missing, we're on CTP bits. All endpoints from the CTP serverInfo should be treated as HTTPS
|
// If endpoint is missing, we're on CTP bits. All endpoints from the CTP serverInfo should be treated as HTTPS
|
||||||
let endpoint = e.endpoint ? e.endpoint : `https://${e.ipAddress}:${e.port}`;
|
let endpoint = e.endpoint ? e.endpoint : `https://${e.ipAddress}:${e.port}`;
|
||||||
let updatedEndpoint: IEndpoint = {
|
let updatedEndpoint: bdc.IEndpointModel = {
|
||||||
serviceName: e.serviceName,
|
name: e.serviceName,
|
||||||
description: e.description,
|
description: e.description,
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
protocol: e.protocol
|
protocol: e.protocol
|
||||||
@@ -210,7 +204,6 @@ export function getClusterEndpoints(serverInfo: azdata.ServerInfo): IEndpoint[]
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export type HostAndIp = { host: string, port: string };
|
export type HostAndIp = { host: string, port: string };
|
||||||
|
|
||||||
export function getHostAndPortFromEndpoint(endpoint: string): HostAndIp {
|
export function getHostAndPortFromEndpoint(endpoint: string): HostAndIp {
|
||||||
@@ -229,6 +222,26 @@ export function getHostAndPortFromEndpoint(endpoint: string): HostAndIp {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isIntegratedAuth(connection: azdata.IConnectionProfile): boolean {
|
||||||
|
return connection.options[AUTHTYPE] && connection.options[AUTHTYPE].toLowerCase() === INTEGRATED_AUTH.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isSparkKernel(kernelName: string): boolean {
|
||||||
|
return kernelName && kernelName.toLowerCase().indexOf('spark') > -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setHostAndPort(delimeter: string, connection: azdata.IConnectionProfile): void {
|
||||||
|
let originalHost = connection.options[KNOX_ENDPOINT_SERVER];
|
||||||
|
if (!originalHost) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let index = originalHost.indexOf(delimeter);
|
||||||
|
if (index > -1) {
|
||||||
|
connection.options[KNOX_ENDPOINT_SERVER] = originalHost.slice(0, index);
|
||||||
|
connection.options[KNOX_ENDPOINT_PORT] = originalHost.slice(index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function exists(path: string): Promise<boolean> {
|
export async function exists(path: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await fs.access(path);
|
await fs.access(path);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { JupyterServerInstallation, PythonPkgDetails } from '../../jupyter/jupyt
|
|||||||
import * as utils from '../../common/utils';
|
import * as utils from '../../common/utils';
|
||||||
import { ManagePackagesDialog } from './managePackagesDialog';
|
import { ManagePackagesDialog } from './managePackagesDialog';
|
||||||
import CodeAdapter from '../../prompts/adapter';
|
import CodeAdapter from '../../prompts/adapter';
|
||||||
import { IQuestion, confirm } from '../../prompts/question';
|
import { IQuestion, QuestionTypes } from '../../prompts/question';
|
||||||
import { IconPathHelper } from '../../common/iconHelper';
|
import { IconPathHelper } from '../../common/iconHelper';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
@@ -255,7 +255,7 @@ export class InstalledPackagesTab {
|
|||||||
|
|
||||||
this.uninstallPackageButton.updateProperties({ enabled: false });
|
this.uninstallPackageButton.updateProperties({ enabled: false });
|
||||||
let doUninstall = await this.prompter.promptSingle<boolean>(<IQuestion>{
|
let doUninstall = await this.prompter.promptSingle<boolean>(<IQuestion>{
|
||||||
type: confirm,
|
type: QuestionTypes.confirm,
|
||||||
message: localize('managePackages.confirmUninstall', "Are you sure you want to uninstall the specified packages?"),
|
message: localize('managePackages.confirmUninstall', "Are you sure you want to uninstall the specified packages?"),
|
||||||
default: false
|
default: false
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import { BuiltInCommands, unsavedBooksContextKey } from './common/constants';
|
|||||||
import { RemoteBookController } from './book/remoteBookController';
|
import { RemoteBookController } from './book/remoteBookController';
|
||||||
import { RemoteBookDialog } from './dialog/remoteBookDialog';
|
import { RemoteBookDialog } from './dialog/remoteBookDialog';
|
||||||
import { RemoteBookDialogModel } from './dialog/remoteBookDialogModel';
|
import { RemoteBookDialogModel } from './dialog/remoteBookDialogModel';
|
||||||
|
import { IconPathHelper } from './common/iconHelper';
|
||||||
|
import { ExtensionContextHelper } from './common/extensionContextHelper';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -24,6 +26,9 @@ let controller: JupyterController;
|
|||||||
type ChooseCellType = { label: string, id: CellType };
|
type ChooseCellType = { label: string, id: CellType };
|
||||||
|
|
||||||
export async function activate(extensionContext: vscode.ExtensionContext): Promise<IExtensionApi> {
|
export async function activate(extensionContext: vscode.ExtensionContext): Promise<IExtensionApi> {
|
||||||
|
ExtensionContextHelper.setExtensionContext(extensionContext);
|
||||||
|
IconPathHelper.setExtensionContext(extensionContext);
|
||||||
|
|
||||||
const appContext = new AppContext(extensionContext);
|
const appContext = new AppContext(extensionContext);
|
||||||
const createBookPath: string = path.posix.join(extensionContext.extensionPath, 'resources', 'notebooks', 'JupyterBooksCreate.ipynb');
|
const createBookPath: string = path.posix.join(extensionContext.extensionPath, 'resources', 'notebooks', 'JupyterBooksCreate.ipynb');
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import * as constants from '../common/constants';
|
|||||||
import * as localizedConstants from '../common/localizedConstants';
|
import * as localizedConstants from '../common/localizedConstants';
|
||||||
import { JupyterServerInstallation } from './jupyterServerInstallation';
|
import { JupyterServerInstallation } from './jupyterServerInstallation';
|
||||||
import * as utils from '../common/utils';
|
import * as utils from '../common/utils';
|
||||||
import { IPrompter, IQuestion, confirm } from '../prompts/question';
|
import { IPrompter, IQuestion, QuestionTypes } from '../prompts/question';
|
||||||
|
|
||||||
import { AppContext } from '../common/appContext';
|
import { AppContext } from '../common/appContext';
|
||||||
import { LocalJupyterServerManager, ServerInstanceFactory } from './jupyterServerManager';
|
import { LocalJupyterServerManager, ServerInstanceFactory } from './jupyterServerManager';
|
||||||
@@ -28,7 +28,6 @@ import { LocalPipPackageManageProvider } from './localPipPackageManageProvider';
|
|||||||
import { LocalCondaPackageManageProvider } from './localCondaPackageManageProvider';
|
import { LocalCondaPackageManageProvider } from './localCondaPackageManageProvider';
|
||||||
import { ManagePackagesDialogModel, ManagePackageDialogOptions } from '../dialog/managePackages/managePackagesDialogModel';
|
import { ManagePackagesDialogModel, ManagePackageDialogOptions } from '../dialog/managePackages/managePackagesDialogModel';
|
||||||
import { PyPiClient } from './pypiClient';
|
import { PyPiClient } from './pypiClient';
|
||||||
import { IconPathHelper } from '../common/iconHelper';
|
|
||||||
|
|
||||||
let untitledCounter = 0;
|
let untitledCounter = 0;
|
||||||
|
|
||||||
@@ -58,7 +57,6 @@ export class JupyterController {
|
|||||||
this.extensionContext.extensionPath,
|
this.extensionContext.extensionPath,
|
||||||
this.appContext.outputChannel);
|
this.appContext.outputChannel);
|
||||||
await this._jupyterInstallation.configurePackagePaths();
|
await this._jupyterInstallation.configurePackagePaths();
|
||||||
IconPathHelper.setExtensionContext(this.extensionContext);
|
|
||||||
|
|
||||||
// Add command/task handlers
|
// Add command/task handlers
|
||||||
azdata.tasks.registerTask(constants.jupyterOpenNotebookTask, (profile: azdata.IConnectionProfile) => {
|
azdata.tasks.registerTask(constants.jupyterOpenNotebookTask, (profile: azdata.IConnectionProfile) => {
|
||||||
@@ -183,7 +181,7 @@ export class JupyterController {
|
|||||||
//Confirmation message dialog
|
//Confirmation message dialog
|
||||||
private async confirmReinstall(): Promise<boolean> {
|
private async confirmReinstall(): Promise<boolean> {
|
||||||
return await this.prompter.promptSingle<boolean>(<IQuestion>{
|
return await this.prompter.promptSingle<boolean>(<IQuestion>{
|
||||||
type: confirm,
|
type: QuestionTypes.confirm,
|
||||||
message: localize('confirmReinstall', "Are you sure you want to reinstall?"),
|
message: localize('confirmReinstall', "Are you sure you want to reinstall?"),
|
||||||
default: true
|
default: true
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { nb, ServerInfo, connection, IConnectionProfile } from 'azdata';
|
import { nb, ServerInfo, connection, IConnectionProfile, credentials } from 'azdata';
|
||||||
import { Session, Kernel } from '@jupyterlab/services';
|
import { Session, Kernel } from '@jupyterlab/services';
|
||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
@@ -18,6 +18,10 @@ import { Deferred } from '../common/promise';
|
|||||||
import { JupyterServerInstallation } from './jupyterServerInstallation';
|
import { JupyterServerInstallation } from './jupyterServerInstallation';
|
||||||
import * as bdc from 'bdc';
|
import * as bdc from 'bdc';
|
||||||
import { noBDCConnectionError, providerNotValidError } from '../common/localizedConstants';
|
import { noBDCConnectionError, providerNotValidError } from '../common/localizedConstants';
|
||||||
|
import { SQL_PROVIDER, CONTROLLER_ENDPOINT, KNOX_ENDPOINT_GATEWAY, KNOX_ENDPOINT_SERVER, KNOX_ENDPOINT_PORT } from '../common/constants';
|
||||||
|
import CodeAdapter from '../prompts/adapter';
|
||||||
|
import { IQuestion, QuestionTypes } from '../prompts/question';
|
||||||
|
import { ExtensionContextHelper } from '../common/extensionContextHelper';
|
||||||
|
|
||||||
const configBase = {
|
const configBase = {
|
||||||
'kernel_python_credentials': {
|
'kernel_python_credentials': {
|
||||||
@@ -55,15 +59,6 @@ const configBase = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const KNOX_ENDPOINT_SERVER = 'host';
|
|
||||||
const KNOX_ENDPOINT_PORT = 'knoxport';
|
|
||||||
const KNOX_ENDPOINT_GATEWAY = 'gateway';
|
|
||||||
const CONTROLLER_ENDPOINT = 'controller';
|
|
||||||
const SQL_PROVIDER = 'MSSQL';
|
|
||||||
const USER = 'user';
|
|
||||||
const AUTHTYPE = 'authenticationType';
|
|
||||||
const INTEGRATED_AUTH = 'integrated';
|
|
||||||
|
|
||||||
export class JupyterSessionManager implements nb.SessionManager {
|
export class JupyterSessionManager implements nb.SessionManager {
|
||||||
private _ready: Deferred<void>;
|
private _ready: Deferred<void>;
|
||||||
private _isReady: boolean;
|
private _isReady: boolean;
|
||||||
@@ -183,7 +178,11 @@ export class JupyterSession implements nb.ISession {
|
|||||||
private _kernel: nb.IKernel;
|
private _kernel: nb.IKernel;
|
||||||
private _messagesComplete: Deferred<void> = new Deferred<void>();
|
private _messagesComplete: Deferred<void> = new Deferred<void>();
|
||||||
|
|
||||||
constructor(private sessionImpl: Session.ISession, private _installation: JupyterServerInstallation, skipSettingEnvironmentVars?: boolean, private _pythonEnvVarPath?: string) {
|
constructor(
|
||||||
|
private sessionImpl: Session.ISession,
|
||||||
|
private _installation: JupyterServerInstallation,
|
||||||
|
skipSettingEnvironmentVars?: boolean,
|
||||||
|
private _pythonEnvVarPath?: string) {
|
||||||
this.setEnvironmentVars(skipSettingEnvironmentVars).catch(error => {
|
this.setEnvironmentVars(skipSettingEnvironmentVars).catch(error => {
|
||||||
console.error(`Unexpected exception setting Jupyter Session variables : ${error}`);
|
console.error(`Unexpected exception setting Jupyter Session variables : ${error}`);
|
||||||
// We don't want callers to hang forever waiting - it's better to continue on even if we weren't
|
// We don't want callers to hang forever waiting - it's better to continue on even if we weren't
|
||||||
@@ -275,53 +274,74 @@ export class JupyterSession implements nb.ISession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async configureConnection(connectionProfile: IConnectionProfile): Promise<void> {
|
public async configureConnection(connectionProfile: IConnectionProfile): Promise<void> {
|
||||||
if (connectionProfile && connectionProfile.providerName && this.isSparkKernel(this.sessionImpl.kernel.name)) {
|
if (connectionProfile && connectionProfile.providerName && utils.isSparkKernel(this.sessionImpl.kernel.name)) {
|
||||||
// %_do_not_call_change_endpoint is a SparkMagic command that lets users change endpoint options,
|
// %_do_not_call_change_endpoint is a SparkMagic command that lets users change endpoint options,
|
||||||
// such as user/profile/host name/auth type
|
// such as user/profile/host name/auth type
|
||||||
|
|
||||||
let credentials;
|
let knoxUsername = connectionProfile.userName || 'root';
|
||||||
if (!this.isIntegratedAuth(connectionProfile)) {
|
let knoxPassword: string = '';
|
||||||
credentials = await connection.getCredentials(connectionProfile.id);
|
|
||||||
}
|
|
||||||
//Update server info with bigdata endpoint - Unified Connection
|
//Update server info with bigdata endpoint - Unified Connection
|
||||||
if (connectionProfile.providerName === SQL_PROVIDER) {
|
if (connectionProfile.providerName === SQL_PROVIDER) {
|
||||||
const endpoints = await this.getClusterEndpoints(connectionProfile.id);
|
const serverInfo: ServerInfo = await connection.getServerInfo(connectionProfile.id);
|
||||||
const gatewayEndpoint: utils.IEndpoint = endpoints?.find(ep => ep.serviceName.toLowerCase() === KNOX_ENDPOINT_GATEWAY);
|
if (!serverInfo?.options['isBigDataCluster']) {
|
||||||
if (!gatewayEndpoint) {
|
|
||||||
throw new Error(noBDCConnectionError);
|
throw new Error(noBDCConnectionError);
|
||||||
}
|
}
|
||||||
|
const endpoints = utils.getClusterEndpoints(serverInfo);
|
||||||
|
const controllerEndpoint = endpoints.find(ep => ep.name.toLowerCase() === CONTROLLER_ENDPOINT);
|
||||||
|
|
||||||
|
// root is the default username for pre-CU5 instances, so while we prefer to use the connection username
|
||||||
|
// as a default now we'll still fall back to root if it's empty for some reason. (but the calls below should
|
||||||
|
// get the actual correct value regardless)
|
||||||
|
let clusterController: bdc.IClusterController | undefined = undefined;
|
||||||
|
if (!utils.isIntegratedAuth(connectionProfile)) {
|
||||||
|
// See if the controller creds have been saved already, otherwise fall back to using
|
||||||
|
// SQL creds as a default
|
||||||
|
const credentialProvider = await credentials.getProvider('notebook.bdc.password');
|
||||||
|
const usernameKey = `notebook.bdc.username::${connectionProfile.id}`;
|
||||||
|
const savedUsername = ExtensionContextHelper.extensionContext.globalState.get<string>(usernameKey) || connectionProfile.userName;
|
||||||
|
const connectionCreds = await connection.getCredentials(connectionProfile.id);
|
||||||
|
const savedPassword = (await credentialProvider.readCredential(connectionProfile.id)).password || connectionCreds.password;
|
||||||
|
clusterController = await getClusterController(controllerEndpoint.endpoint, 'basic', savedUsername, savedPassword);
|
||||||
|
// Now that we know that the username/password are valid store them for use later on with the same connection
|
||||||
|
await credentialProvider.saveCredential(connectionProfile.id, clusterController.password);
|
||||||
|
await ExtensionContextHelper.extensionContext.globalState.update(usernameKey, clusterController.username);
|
||||||
|
knoxPassword = clusterController.password;
|
||||||
|
try {
|
||||||
|
knoxUsername = await clusterController.getKnoxUsername(clusterController.username);
|
||||||
|
} catch (err) {
|
||||||
|
knoxUsername = clusterController.username;
|
||||||
|
console.log(`Unexpected error getting Knox username for Spark kernel: ${err}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clusterController = await getClusterController(controllerEndpoint.endpoint, 'integrated');
|
||||||
|
}
|
||||||
|
|
||||||
|
let gatewayEndpoint: bdc.IEndpointModel = endpoints?.find(ep => ep.name.toLowerCase() === KNOX_ENDPOINT_GATEWAY);
|
||||||
|
if (!gatewayEndpoint) {
|
||||||
|
// User doesn't have permission to see the gateway endpoint from the DMV so we need to query the controller instead
|
||||||
|
const allEndpoints = (await clusterController.getEndPoints()).endPoints;
|
||||||
|
gatewayEndpoint = allEndpoints?.find(ep => ep.name.toLowerCase() === KNOX_ENDPOINT_GATEWAY);
|
||||||
|
if (!gatewayEndpoint) {
|
||||||
|
throw new Error(localize('notebook.couldNotFindKnoxGateway', "Could not find Knox gateway endpoint"));
|
||||||
|
}
|
||||||
|
}
|
||||||
let gatewayHostAndPort = utils.getHostAndPortFromEndpoint(gatewayEndpoint.endpoint);
|
let gatewayHostAndPort = utils.getHostAndPortFromEndpoint(gatewayEndpoint.endpoint);
|
||||||
connectionProfile.options[KNOX_ENDPOINT_SERVER] = gatewayHostAndPort.host;
|
connectionProfile.options[KNOX_ENDPOINT_SERVER] = gatewayHostAndPort.host;
|
||||||
connectionProfile.options[KNOX_ENDPOINT_PORT] = gatewayHostAndPort.port;
|
connectionProfile.options[KNOX_ENDPOINT_PORT] = gatewayHostAndPort.port;
|
||||||
// root is the default username for pre-CU5 instances, so while we prefer to use the connection username
|
|
||||||
// as a default now we'll still fall back to root if it's empty for some reason. (but the calls below should
|
|
||||||
// get the actual correct value regardless)
|
|
||||||
connectionProfile.options[USER] = connectionProfile.userName || 'root';
|
|
||||||
if (!this.isIntegratedAuth(connectionProfile)) {
|
|
||||||
try {
|
|
||||||
const bdcApi = <bdc.IExtension>await vscode.extensions.getExtension(bdc.constants.extensionName).activate();
|
|
||||||
const controllerEndpoint = endpoints.find(ep => ep.serviceName.toLowerCase() === CONTROLLER_ENDPOINT);
|
|
||||||
const controller = bdcApi.getClusterController(controllerEndpoint.endpoint, 'basic', connectionProfile.userName, credentials.password);
|
|
||||||
connectionProfile.options[USER] = await controller.getKnoxUsername(connectionProfile.userName);
|
|
||||||
} catch (err) {
|
|
||||||
console.log(`Unexpected error getting Knox username for Spark kernel: ${err}`);
|
|
||||||
// Optimistically use the SQL login name - that's going to normally be the case after CU5
|
|
||||||
connectionProfile.options[USER] = connectionProfile.userName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new Error(providerNotValidError);
|
throw new Error(providerNotValidError);
|
||||||
}
|
}
|
||||||
this.setHostAndPort(':', connectionProfile);
|
utils.setHostAndPort(':', connectionProfile);
|
||||||
this.setHostAndPort(',', connectionProfile);
|
utils.setHostAndPort(',', connectionProfile);
|
||||||
|
|
||||||
let server = vscode.Uri.parse(utils.getLivyUrl(connectionProfile.options[KNOX_ENDPOINT_SERVER], connectionProfile.options[KNOX_ENDPOINT_PORT])).toString();
|
let server = vscode.Uri.parse(utils.getLivyUrl(connectionProfile.options[KNOX_ENDPOINT_SERVER], connectionProfile.options[KNOX_ENDPOINT_PORT])).toString();
|
||||||
let doNotCallChangeEndpointParams: string;
|
let doNotCallChangeEndpointParams: string;
|
||||||
if (this.isIntegratedAuth(connectionProfile)) {
|
if (utils.isIntegratedAuth(connectionProfile)) {
|
||||||
doNotCallChangeEndpointParams = `%_do_not_call_change_endpoint --server=${server} --auth=Kerberos`;
|
doNotCallChangeEndpointParams = `%_do_not_call_change_endpoint --server=${server} --auth=Kerberos`;
|
||||||
} else {
|
} else {
|
||||||
doNotCallChangeEndpointParams = `%_do_not_call_change_endpoint --username=${connectionProfile.options[USER]} --password=${credentials.password} --server=${server} --auth=Basic_Access`;
|
doNotCallChangeEndpointParams = `%_do_not_call_change_endpoint --username=${knoxUsername} --password=${knoxPassword} --server=${server} --auth=Basic_Access`;
|
||||||
}
|
}
|
||||||
let future = this.sessionImpl.kernel.requestExecute({
|
let future = this.sessionImpl.kernel.requestExecute({
|
||||||
code: doNotCallChangeEndpointParams
|
code: doNotCallChangeEndpointParams
|
||||||
@@ -330,26 +350,6 @@ export class JupyterSession implements nb.ISession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private isIntegratedAuth(connection: IConnectionProfile): boolean {
|
|
||||||
return connection.options[AUTHTYPE] && connection.options[AUTHTYPE].toLowerCase() === INTEGRATED_AUTH.toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
private isSparkKernel(kernelName: string): boolean {
|
|
||||||
return kernelName && kernelName.toLowerCase().indexOf('spark') > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
private setHostAndPort(delimeter: string, connection: IConnectionProfile): void {
|
|
||||||
let originalHost = connection.options[KNOX_ENDPOINT_SERVER];
|
|
||||||
if (!originalHost) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let index = originalHost.indexOf(delimeter);
|
|
||||||
if (index > -1) {
|
|
||||||
connection.options[KNOX_ENDPOINT_SERVER] = originalHost.slice(0, index);
|
|
||||||
connection.options[KNOX_ENDPOINT_PORT] = originalHost.slice(index + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateConfig(config: ISparkMagicConfig, creds: ICredentials, homePath: string): void {
|
private updateConfig(config: ISparkMagicConfig, creds: ICredentials, homePath: string): void {
|
||||||
config.kernel_python_credentials = creds;
|
config.kernel_python_credentials = creds;
|
||||||
config.kernel_scala_credentials = creds;
|
config.kernel_scala_credentials = creds;
|
||||||
@@ -358,14 +358,6 @@ export class JupyterSession implements nb.ISession {
|
|||||||
config.ignore_ssl_errors = utils.getIgnoreSslVerificationConfigSetting();
|
config.ignore_ssl_errors = utils.getIgnoreSslVerificationConfigSetting();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getClusterEndpoints(profileId: string): Promise<utils.IEndpoint[]> {
|
|
||||||
let serverInfo: ServerInfo = await connection.getServerInfo(profileId);
|
|
||||||
if (!serverInfo || !serverInfo.options) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return utils.getClusterEndpoints(serverInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async setEnvironmentVars(skip: boolean = false): Promise<void> {
|
private async setEnvironmentVars(skip: boolean = false): Promise<void> {
|
||||||
// The PowerShell kernel doesn't define the %cd and %set_env magics; no need to run those here then
|
// The PowerShell kernel doesn't define the %cd and %set_env magics; no need to run those here then
|
||||||
if (!skip && this.sessionImpl?.kernel?.name !== 'powershell') {
|
if (!skip && this.sessionImpl?.kernel?.name !== 'powershell') {
|
||||||
@@ -395,6 +387,55 @@ export class JupyterSession implements nb.ISession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getClusterController(controllerEndpoint: string, authType: bdc.AuthType, username?: string, password?: string): Promise<bdc.IClusterController | undefined> {
|
||||||
|
const bdcApi = <bdc.IExtension>await vscode.extensions.getExtension(bdc.constants.extensionName).activate();
|
||||||
|
const controller = bdcApi.getClusterController(
|
||||||
|
controllerEndpoint,
|
||||||
|
authType,
|
||||||
|
username,
|
||||||
|
password);
|
||||||
|
try {
|
||||||
|
await controller.getClusterConfig();
|
||||||
|
return controller;
|
||||||
|
} catch (err) {
|
||||||
|
// Initial username/password failed so prompt user for username password until either user
|
||||||
|
// cancels out or we successfully connect
|
||||||
|
console.log(`Error connecting to cluster controller: ${err}`);
|
||||||
|
let errorMessage = '';
|
||||||
|
const prompter = new CodeAdapter();
|
||||||
|
while (true) {
|
||||||
|
const newUsername = await prompter.promptSingle<string>(<IQuestion>{
|
||||||
|
type: QuestionTypes.input,
|
||||||
|
name: 'inputPrompt',
|
||||||
|
message: localize('promptBDCUsername', "{0}Please provide the username to connect to the BDC Controller:", errorMessage),
|
||||||
|
default: username
|
||||||
|
});
|
||||||
|
if (!username) {
|
||||||
|
console.log(`User cancelled out of username prompt for BDC Controller`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const newPassword = await prompter.promptSingle<string>(<IQuestion>{
|
||||||
|
type: QuestionTypes.password,
|
||||||
|
name: 'passwordPrompt',
|
||||||
|
message: localize('promptBDCPassword', "Please provide the password to connect to the BDC Controller"),
|
||||||
|
default: ''
|
||||||
|
});
|
||||||
|
if (!password) {
|
||||||
|
console.log(`User cancelled out of password prompt for BDC Controller`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const controller = bdcApi.getClusterController(controllerEndpoint, authType, newUsername, newPassword);
|
||||||
|
try {
|
||||||
|
await controller.getClusterConfig();
|
||||||
|
return controller;
|
||||||
|
} catch (err) {
|
||||||
|
errorMessage = localize('bdcConnectError', "Error: {0}. ", err.message ?? err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new Error(localize('clusterControllerConnectionRequired', "A connection to the cluster controller is required to run Spark jobs"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface ICredentials {
|
interface ICredentials {
|
||||||
'url': string;
|
'url': string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import * as vscode from 'vscode';
|
|||||||
|
|
||||||
export default class ConfirmPrompt extends Prompt {
|
export default class ConfirmPrompt extends Prompt {
|
||||||
|
|
||||||
constructor(question: any) {
|
constructor(question: any, ignoreFocusOut?: boolean) {
|
||||||
super(question);
|
super(question, ignoreFocusOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(): any {
|
public render(): any {
|
||||||
|
|||||||
@@ -3,13 +3,19 @@
|
|||||||
|
|
||||||
import Prompt from './prompt';
|
import Prompt from './prompt';
|
||||||
import ConfirmPrompt from './confirm';
|
import ConfirmPrompt from './confirm';
|
||||||
|
import InputPrompt from './input';
|
||||||
|
import PasswordPrompt from './password';
|
||||||
|
|
||||||
export default class PromptFactory {
|
export default class PromptFactory {
|
||||||
|
|
||||||
public static createPrompt(question: any): Prompt {
|
public static createPrompt(question: any, ignoreFocusOut?: boolean): Prompt {
|
||||||
switch (question.type) {
|
switch (question.type) {
|
||||||
|
case 'input':
|
||||||
|
return new InputPrompt(question, ignoreFocusOut);
|
||||||
|
case 'password':
|
||||||
|
return new PasswordPrompt(question, ignoreFocusOut);
|
||||||
case 'confirm':
|
case 'confirm':
|
||||||
return new ConfirmPrompt(question);
|
return new ConfirmPrompt(question, ignoreFocusOut);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Could not find a prompt for question type ${question.type}`);
|
throw new Error(`Could not find a prompt for question type ${question.type}`);
|
||||||
}
|
}
|
||||||
|
|||||||
57
extensions/notebook/src/prompts/input.ts
Normal file
57
extensions/notebook/src/prompts/input.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// This code is originally from https://github.com/DonJayamanne/bowerVSCode
|
||||||
|
// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
|
||||||
|
|
||||||
|
import { window, InputBoxOptions } from 'vscode';
|
||||||
|
import Prompt from './prompt';
|
||||||
|
import EscapeException from './escapeException';
|
||||||
|
|
||||||
|
const figures = require('figures');
|
||||||
|
|
||||||
|
export default class InputPrompt extends Prompt {
|
||||||
|
|
||||||
|
protected _options: InputBoxOptions;
|
||||||
|
|
||||||
|
constructor(question: any, ignoreFocusOut?: boolean) {
|
||||||
|
super(question, ignoreFocusOut);
|
||||||
|
|
||||||
|
this._options = this.defaultInputBoxOptions;
|
||||||
|
this._options.prompt = this._question.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for callers to know the right type to get from the type factory
|
||||||
|
public static get promptType(): string { return 'input'; }
|
||||||
|
|
||||||
|
public render(): any {
|
||||||
|
// Prefer default over the placeHolder, if specified
|
||||||
|
let placeHolder = this._question.default ? this._question.default : this._question.placeHolder;
|
||||||
|
|
||||||
|
if (this._question.default instanceof Error) {
|
||||||
|
placeHolder = this._question.default.message;
|
||||||
|
this._question.default = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._options.placeHolder = placeHolder;
|
||||||
|
|
||||||
|
return window.showInputBox(this._options)
|
||||||
|
.then(result => {
|
||||||
|
if (result === undefined) {
|
||||||
|
throw new EscapeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result === '') {
|
||||||
|
// Use the default value, if defined
|
||||||
|
result = this._question.default || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const validationError = this._question.validate ? this._question.validate(result || '') : undefined;
|
||||||
|
|
||||||
|
if (validationError) {
|
||||||
|
this._question.default = new Error(`${figures.warning} ${validationError}`);
|
||||||
|
|
||||||
|
return this.render();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
13
extensions/notebook/src/prompts/password.ts
Normal file
13
extensions/notebook/src/prompts/password.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// This code is originally from https://github.com/DonJayamanne/bowerVSCode
|
||||||
|
// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
|
||||||
|
|
||||||
|
import InputPrompt from './input';
|
||||||
|
|
||||||
|
export default class PasswordPrompt extends InputPrompt {
|
||||||
|
|
||||||
|
constructor(question: any, ignoreFocusOut?: boolean) {
|
||||||
|
super(question, ignoreFocusOut);
|
||||||
|
|
||||||
|
this._options.password = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,11 @@
|
|||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
export const confirm = 'confirm';
|
export const enum QuestionTypes {
|
||||||
|
input = 'input',
|
||||||
|
password = 'password',
|
||||||
|
confirm = 'confirm'
|
||||||
|
}
|
||||||
|
|
||||||
// Question interface to clarify how to use the prompt feature
|
// Question interface to clarify how to use the prompt feature
|
||||||
// based on Bower Question format: https://github.com/bower/bower/blob/89069784bb46bfd6639b4a75e98a0d7399a8c2cb/packages/bower-logger/README.md
|
// based on Bower Question format: https://github.com/bower/bower/blob/89069784bb46bfd6639b4a75e98a0d7399a8c2cb/packages/bower-logger/README.md
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import * as path from 'path';
|
|||||||
import * as querystring from 'querystring';
|
import * as querystring from 'querystring';
|
||||||
import * as nls from 'vscode-nls';
|
import * as nls from 'vscode-nls';
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
import { IQuestion, confirm } from '../prompts/question';
|
import { IQuestion, QuestionTypes } from '../prompts/question';
|
||||||
import CodeAdapter from '../prompts/adapter';
|
import CodeAdapter from '../prompts/adapter';
|
||||||
import { getErrorMessage, isEditorTitleFree } from '../common/utils';
|
import { getErrorMessage, isEditorTitleFree } from '../common/utils';
|
||||||
import * as constants from '../common/constants';
|
import * as constants from '../common/constants';
|
||||||
@@ -61,7 +61,7 @@ export class NotebookUriHandler implements vscode.UriHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let doOpen = await this.prompter.promptSingle<boolean>(<IQuestion>{
|
let doOpen = await this.prompter.promptSingle<boolean>(<IQuestion>{
|
||||||
type: confirm,
|
type: QuestionTypes.confirm,
|
||||||
message: localize('notebook.confirmOpen', "Download and open '{0}'?", url),
|
message: localize('notebook.confirmOpen', "Download and open '{0}'?", url),
|
||||||
default: true
|
default: true
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as should from 'should';
|
|||||||
import * as sinon from 'sinon';
|
import * as sinon from 'sinon';
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
import { IPrompter, confirm, IQuestion } from '../../prompts/question';
|
import { IPrompter, IQuestion, QuestionTypes } from '../../prompts/question';
|
||||||
import CodeAdapter from '../../prompts/adapter';
|
import CodeAdapter from '../../prompts/adapter';
|
||||||
|
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ describe('Prompt', () => {
|
|||||||
it('Should find prompt for confirm type', async function (): Promise<void> {
|
it('Should find prompt for confirm type', async function (): Promise<void> {
|
||||||
const quickPickSpy = sinon.stub(vscode.window, 'showQuickPick').returns(Promise.resolve('Yes') as any);
|
const quickPickSpy = sinon.stub(vscode.window, 'showQuickPick').returns(Promise.resolve('Yes') as any);
|
||||||
await prompter.promptSingle<boolean>(<IQuestion>{
|
await prompter.promptSingle<boolean>(<IQuestion>{
|
||||||
type: confirm,
|
type: QuestionTypes.confirm,
|
||||||
message: 'sample message',
|
message: 'sample message',
|
||||||
default: false
|
default: false
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,10 +19,13 @@ import { JupyterSessionManager, JupyterSession } from '../../jupyter/jupyterSess
|
|||||||
import { Deferred } from '../../common/promise';
|
import { Deferred } from '../../common/promise';
|
||||||
import { SessionStub, KernelStub, FutureStub } from '../common';
|
import { SessionStub, KernelStub, FutureStub } from '../common';
|
||||||
import { noBDCConnectionError, providerNotValidError } from '../../common/localizedConstants';
|
import { noBDCConnectionError, providerNotValidError } from '../../common/localizedConstants';
|
||||||
|
import { ExtensionContextHelper } from '../../common/extensionContextHelper';
|
||||||
|
import { AppContext } from '../../common/appContext';
|
||||||
|
import uuid = require('uuid');
|
||||||
|
|
||||||
export class TestClusterController implements bdc.IClusterController {
|
export class TestClusterController implements bdc.IClusterController {
|
||||||
getClusterConfig(): Promise<any> {
|
getClusterConfig(): Promise<any> {
|
||||||
throw new Error('Method not implemented.');
|
return Promise.resolve({});
|
||||||
}
|
}
|
||||||
getKnoxUsername(clusterUsername: string): Promise<string> {
|
getKnoxUsername(clusterUsername: string): Promise<string> {
|
||||||
return Promise.resolve('knoxUsername');
|
return Promise.resolve('knoxUsername');
|
||||||
@@ -33,8 +36,17 @@ export class TestClusterController implements bdc.IClusterController {
|
|||||||
endPoints: []
|
endPoints: []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
before(async function(): Promise<void> {
|
||||||
|
// We have to reset the extension context here since the test runner unloads the files before running the tests
|
||||||
|
// so the static state is lost
|
||||||
|
const api = await vscode.extensions.getExtension('Microsoft.notebook').activate();
|
||||||
|
ExtensionContextHelper.setExtensionContext((api.getAppContext() as AppContext).extensionContext);
|
||||||
|
});
|
||||||
|
|
||||||
describe('Jupyter Session Manager', function (): void {
|
describe('Jupyter Session Manager', function (): void {
|
||||||
let mockJupyterManager = TypeMoq.Mock.ofType<SessionManager>();
|
let mockJupyterManager = TypeMoq.Mock.ofType<SessionManager>();
|
||||||
let sessionManager = new JupyterSessionManager();
|
let sessionManager = new JupyterSessionManager();
|
||||||
@@ -276,25 +288,29 @@ describe('Jupyter Session', function (): void {
|
|||||||
isCloud: false,
|
isCloud: false,
|
||||||
azureVersion: 0,
|
azureVersion: 0,
|
||||||
osVersion: '',
|
osVersion: '',
|
||||||
options: {}
|
options: {
|
||||||
|
isBigDataCluster: true
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const mockGatewayEndpoint: utils.IEndpoint = {
|
const mockGatewayEndpoint: bdc.IEndpointModel = {
|
||||||
serviceName: 'gateway',
|
name: 'gateway',
|
||||||
description: '',
|
description: '',
|
||||||
endpoint: '',
|
endpoint: '',
|
||||||
protocol: '',
|
protocol: '',
|
||||||
};
|
};
|
||||||
const mockControllerEndpoint: utils.IEndpoint = {
|
const mockControllerEndpoint: bdc.IEndpointModel = {
|
||||||
serviceName: 'controller',
|
name: 'controller',
|
||||||
description: '',
|
description: '',
|
||||||
endpoint: '',
|
endpoint: '',
|
||||||
protocol: '',
|
protocol: '',
|
||||||
};
|
};
|
||||||
const mockHostAndIp: utils.HostAndIp = {
|
const mockHostAndIp: utils.HostAndIp = {
|
||||||
host: 'host',
|
host: '127.0.0.1',
|
||||||
port: 'port'
|
port: '1337'
|
||||||
};
|
};
|
||||||
const mockClustercontroller = new TestClusterController();
|
const mockClustercontroller = new TestClusterController();
|
||||||
|
mockClustercontroller.username = 'admin';
|
||||||
|
mockClustercontroller.password = uuid.v4();
|
||||||
let mockBdcExtension: TypeMoq.IMock<bdc.IExtension> = TypeMoq.Mock.ofType<bdc.IExtension>();
|
let mockBdcExtension: TypeMoq.IMock<bdc.IExtension> = TypeMoq.Mock.ofType<bdc.IExtension>();
|
||||||
let mockExtension: TypeMoq.IMock<vscode.Extension<any>> = TypeMoq.Mock.ofType<vscode.Extension<any>>();
|
let mockExtension: TypeMoq.IMock<vscode.Extension<any>> = TypeMoq.Mock.ofType<vscode.Extension<any>>();
|
||||||
mockBdcExtension.setup(m => m.getClusterController(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => mockClustercontroller);
|
mockBdcExtension.setup(m => m.getClusterController(TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => mockClustercontroller);
|
||||||
@@ -307,9 +323,8 @@ describe('Jupyter Session', function (): void {
|
|||||||
sinon.stub(utils, 'getHostAndPortFromEndpoint').returns(mockHostAndIp);
|
sinon.stub(utils, 'getHostAndPortFromEndpoint').returns(mockHostAndIp);
|
||||||
|
|
||||||
await session.configureConnection(connectionProfile);
|
await session.configureConnection(connectionProfile);
|
||||||
should(connectionProfile.options['host']).equal('host');
|
should(connectionProfile.options['host']).equal(mockHostAndIp.host);
|
||||||
should(connectionProfile.options['knoxport']).equal('port');
|
should(connectionProfile.options['knoxport']).equal(mockHostAndIp.port);
|
||||||
should(connectionProfile.options['user']).equal('knoxUsername');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('configure connection should throw error if there is no connection to big data cluster', async function (): Promise<void> {
|
it('configure connection should throw error if there is no connection to big data cluster', async function (): Promise<void> {
|
||||||
|
|||||||
@@ -418,17 +418,6 @@ export class TreeItem extends vsExtTypes.TreeItem {
|
|||||||
providerHandle?: string;
|
providerHandle?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServerInfoOption {
|
|
||||||
isBigDataCluster: boolean;
|
|
||||||
clusterEndpoints: ClusterEndpoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ClusterEndpoint {
|
|
||||||
serviceName: string;
|
|
||||||
ipAddress: string;
|
|
||||||
port: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SqlThemeIcon {
|
export class SqlThemeIcon {
|
||||||
|
|
||||||
static readonly Folder = new SqlThemeIcon('Folder');
|
static readonly Folder = new SqlThemeIcon('Folder');
|
||||||
|
|||||||
Reference in New Issue
Block a user