mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-26 17:23:15 -05:00
Allow non-admin BDC connections to see BDC features (#12663)
* Add handling for non-admin BDC users * Bump STS * Fix HDFS root node commands * remove nested awaits * colon
This commit is contained in:
@@ -406,7 +406,7 @@ export class ManageAccessCommand extends Command {
|
||||
try {
|
||||
let node = await getNode<HdfsFileSourceNode>(context, this.appContext);
|
||||
if (node) {
|
||||
new ManageAccessDialog(node.hdfsPath, node.fileSource).openDialog();
|
||||
new ManageAccessDialog(node.hdfsPath, await node.getFileSource()).openDialog();
|
||||
} else {
|
||||
vscode.window.showErrorMessage(LocalizedConstants.msgMissingNodeContext);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import { TreeNode } from './treeNodes';
|
||||
import * as utils from '../utils';
|
||||
import { IFileNode } from './types';
|
||||
import { MountStatus } from '../hdfs/mount';
|
||||
import { SqlClusterSession } from './objectExplorerNodeProvider';
|
||||
|
||||
export interface ITreeChangeHandler {
|
||||
notifyNodeChanged(node: TreeNode): void;
|
||||
@@ -29,8 +30,8 @@ export class TreeDataContext {
|
||||
}
|
||||
|
||||
export abstract class HdfsFileSourceNode extends TreeNode {
|
||||
constructor(protected context: TreeDataContext, protected _path: string, public readonly fileSource: IFileSource, protected mountStatus?: MountStatus) {
|
||||
super();
|
||||
constructor(protected context: TreeDataContext, protected _path: string, fileSource: IFileSource | undefined, protected mountStatus?: MountStatus) {
|
||||
super(fileSource);
|
||||
}
|
||||
|
||||
public get hdfsPath(): string {
|
||||
@@ -51,7 +52,8 @@ export abstract class HdfsFileSourceNode extends TreeNode {
|
||||
}
|
||||
|
||||
public async delete(recursive: boolean = false): Promise<void> {
|
||||
await this.fileSource.delete(this.hdfsPath, recursive);
|
||||
const fileSource = await this.getFileSource();
|
||||
await fileSource.delete(this.hdfsPath, recursive);
|
||||
// Notify parent should be updated. If at top, will return undefined which will refresh whole tree
|
||||
(<HdfsFileSourceNode>this.parent).onChildRemoved();
|
||||
this.context.changeHandler.notifyNodeChanged(this.parent);
|
||||
@@ -60,34 +62,28 @@ export abstract class HdfsFileSourceNode extends TreeNode {
|
||||
}
|
||||
|
||||
export class FolderNode extends HdfsFileSourceNode {
|
||||
private children: TreeNode[];
|
||||
private children: TreeNode[] = [];
|
||||
protected _nodeType: string;
|
||||
constructor(context: TreeDataContext, path: string, fileSource: IFileSource, nodeType?: string, mountStatus?: MountStatus) {
|
||||
constructor(context: TreeDataContext, path: string, fileSource: IFileSource | undefined, nodeType?: string, mountStatus?: MountStatus) {
|
||||
super(context, path, fileSource, mountStatus);
|
||||
this._nodeType = nodeType ? nodeType : Constants.MssqlClusterItems.Folder;
|
||||
}
|
||||
|
||||
private ensureChildrenExist(): void {
|
||||
if (!this.children) {
|
||||
this.children = [];
|
||||
}
|
||||
}
|
||||
|
||||
public onChildRemoved(): void {
|
||||
this.children = undefined;
|
||||
}
|
||||
|
||||
async getChildren(refreshChildren: boolean): Promise<TreeNode[]> {
|
||||
if (refreshChildren || !this.children) {
|
||||
this.ensureChildrenExist();
|
||||
try {
|
||||
let files: IFile[] = await this.fileSource.enumerateFiles(this._path);
|
||||
const fileSource = await this.getFileSource();
|
||||
let files: IFile[] = await fileSource.enumerateFiles(this._path);
|
||||
if (files) {
|
||||
// Note: for now, assuming HDFS-provided sorting is sufficient
|
||||
this.children = files.map((file) => {
|
||||
let node: TreeNode = file.fileType === FileType.File ?
|
||||
new FileNode(this.context, file.path, this.fileSource, this.getChildMountStatus(file)) :
|
||||
new FolderNode(this.context, file.path, this.fileSource, Constants.MssqlClusterItems.Folder, this.getChildMountStatus(file));
|
||||
new FileNode(this.context, file.path, fileSource, this.getChildMountStatus(file)) :
|
||||
new FolderNode(this.context, file.path, fileSource, Constants.MssqlClusterItems.Folder, this.getChildMountStatus(file));
|
||||
node.parent = this;
|
||||
return node;
|
||||
});
|
||||
@@ -153,8 +149,9 @@ export class FolderNode extends HdfsFileSourceNode {
|
||||
}
|
||||
|
||||
private async writeFileAsync(localFile: IFile): Promise<FileNode> {
|
||||
await this.fileSource.writeFile(localFile, this._path);
|
||||
let fileNode = new FileNode(this.context, File.createPath(this._path, File.getBasename(localFile)), this.fileSource);
|
||||
const fileSource = await this.getFileSource();
|
||||
await fileSource.writeFile(localFile, this._path);
|
||||
let fileNode = new FileNode(this.context, File.createPath(this._path, File.getBasename(localFile)), fileSource);
|
||||
return fileNode;
|
||||
}
|
||||
|
||||
@@ -163,8 +160,9 @@ export class FolderNode extends HdfsFileSourceNode {
|
||||
}
|
||||
|
||||
private async mkdirAsync(name: string): Promise<FolderNode> {
|
||||
await this.fileSource.mkdir(name, this._path);
|
||||
let subDir = new FolderNode(this.context, File.createPath(this._path, name), this.fileSource);
|
||||
const fileSource = await this.getFileSource();
|
||||
await fileSource.mkdir(name, this._path);
|
||||
let subDir = new FolderNode(this.context, File.createPath(this._path, name), fileSource);
|
||||
return subDir;
|
||||
}
|
||||
|
||||
@@ -186,8 +184,8 @@ export class FolderNode extends HdfsFileSourceNode {
|
||||
|
||||
export class ConnectionNode extends FolderNode {
|
||||
|
||||
constructor(context: TreeDataContext, private displayName: string, fileSource: IFileSource) {
|
||||
super(context, '/', fileSource, Constants.MssqlClusterItems.Connection);
|
||||
constructor(context: TreeDataContext, private displayName: string, private clusterSession: SqlClusterSession) {
|
||||
super(context, '/', undefined, Constants.MssqlClusterItems.Connection);
|
||||
}
|
||||
|
||||
getDisplayName(): string {
|
||||
@@ -204,6 +202,16 @@ export class ConnectionNode extends FolderNode {
|
||||
return item;
|
||||
}
|
||||
|
||||
public async getFileSource(): Promise<IFileSource | undefined> {
|
||||
// The node is initially created without a filesource and then one is created only once an action is
|
||||
// taken that requires a connection
|
||||
const fileSource = await super.getFileSource();
|
||||
if (!fileSource) {
|
||||
await this.updateFileSource(await this.clusterSession.getSqlClusterConnection());
|
||||
}
|
||||
return super.getFileSource();
|
||||
}
|
||||
|
||||
getNodeInfo(): azdata.NodeInfo {
|
||||
// TODO handle error message case by returning it in the OE API
|
||||
// TODO support better mapping of node type
|
||||
@@ -264,18 +272,21 @@ export class FileNode extends HdfsFileSourceNode implements IFileNode {
|
||||
}
|
||||
|
||||
public async getFileContentsAsString(maxBytes?: number): Promise<string> {
|
||||
let contents: Buffer = await this.fileSource.readFile(this.hdfsPath, maxBytes);
|
||||
const fileSource = await this.getFileSource();
|
||||
let contents: Buffer = await fileSource.readFile(this.hdfsPath, maxBytes);
|
||||
return contents ? contents.toString('utf8') : '';
|
||||
}
|
||||
|
||||
public async getFileLinesAsString(maxLines: number): Promise<string> {
|
||||
let contents: Buffer = await this.fileSource.readFileLines(this.hdfsPath, maxLines);
|
||||
const fileSource = await this.getFileSource();
|
||||
let contents: Buffer = await fileSource.readFileLines(this.hdfsPath, maxLines);
|
||||
return contents ? contents.toString('utf8') : '';
|
||||
}
|
||||
|
||||
public writeFileContentsToDisk(localPath: string, cancelToken?: vscode.CancellationTokenSource): Promise<vscode.Uri> {
|
||||
public async writeFileContentsToDisk(localPath: string, cancelToken?: vscode.CancellationTokenSource): Promise<vscode.Uri> {
|
||||
const fileSource = await this.getFileSource();
|
||||
return new Promise((resolve, reject) => {
|
||||
let readStream: fs.ReadStream = this.fileSource.createReadStream(this.hdfsPath);
|
||||
let readStream: fs.ReadStream = fileSource.createReadStream(this.hdfsPath);
|
||||
readStream.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
@@ -320,7 +331,7 @@ class ErrorNode extends TreeNode {
|
||||
|
||||
private _nodePathValue: string;
|
||||
constructor(private message: string) {
|
||||
super();
|
||||
super(undefined);
|
||||
}
|
||||
|
||||
public static create(message: string, parent: TreeNode, errorCode?: number): ErrorNode {
|
||||
|
||||
@@ -13,23 +13,22 @@ import { SqlClusterConnection } from './connection';
|
||||
import * as utils from '../utils';
|
||||
import { TreeNode } from './treeNodes';
|
||||
import { ConnectionNode, TreeDataContext, ITreeChangeHandler } from './hdfsProvider';
|
||||
import { IFileSource } from './fileSources';
|
||||
import { AppContext } from '../appContext';
|
||||
import * as constants from '../constants';
|
||||
import * as SqlClusterLookUp from '../sqlClusterLookUp';
|
||||
import { ICommandObjectExplorerContext } from './command';
|
||||
import { IPrompter, IQuestion, QuestionTypes } from '../prompts/question';
|
||||
import { getSqlClusterConnectionParams } from '../sqlClusterLookUp';
|
||||
|
||||
export const mssqlOutputChannel = vscode.window.createOutputChannel(constants.providerId);
|
||||
|
||||
export class MssqlObjectExplorerNodeProvider extends ProviderBase implements azdata.ObjectExplorerNodeProvider, ITreeChangeHandler {
|
||||
public readonly supportedProviderId: string = constants.providerId;
|
||||
private sessionMap: Map<string, SqlClusterSession>;
|
||||
private clusterSessionMap: Map<string, SqlClusterSession>;
|
||||
private expandCompleteEmitter = new vscode.EventEmitter<azdata.ObjectExplorerExpandInfo>();
|
||||
|
||||
constructor(private prompter: IPrompter, private appContext: AppContext) {
|
||||
super();
|
||||
this.sessionMap = new Map<string, SqlClusterSession>();
|
||||
this.clusterSessionMap = new Map<string, SqlClusterSession>();
|
||||
this.appContext.registerService<MssqlObjectExplorerNodeProvider>(constants.ObjectExplorerService, this);
|
||||
}
|
||||
|
||||
@@ -49,12 +48,8 @@ export class MssqlObjectExplorerNodeProvider extends ProviderBase implements azd
|
||||
let sqlConnProfile = await azdata.objectexplorer.getSessionConnectionProfile(session.sessionId);
|
||||
if (!sqlConnProfile) { return false; }
|
||||
|
||||
let clusterConnInfo = await SqlClusterLookUp.getSqlClusterConnection(sqlConnProfile);
|
||||
if (!clusterConnInfo) { return false; }
|
||||
|
||||
let clusterConnection = new SqlClusterConnection(clusterConnInfo);
|
||||
let clusterSession = new SqlClusterSession(clusterConnection, session, sqlConnProfile, this.appContext, this);
|
||||
this.sessionMap.set(session.sessionId, clusterSession);
|
||||
let clusterSession = new SqlClusterSession(session, sqlConnProfile, this.appContext, this);
|
||||
this.clusterSessionMap.set(session.sessionId, clusterSession);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -69,7 +64,7 @@ export class MssqlObjectExplorerNodeProvider extends ProviderBase implements azd
|
||||
}
|
||||
|
||||
private async doExpandNode(nodeInfo: azdata.ExpandNodeInfo, isRefresh: boolean = false): Promise<boolean> {
|
||||
let session = this.sessionMap.get(nodeInfo.sessionId);
|
||||
let session = this.clusterSessionMap.get(nodeInfo.sessionId);
|
||||
let response: azdata.ObjectExplorerExpandInfo = {
|
||||
sessionId: nodeInfo.sessionId,
|
||||
nodePath: nodeInfo.nodePath,
|
||||
@@ -117,20 +112,31 @@ export class MssqlObjectExplorerNodeProvider extends ProviderBase implements azd
|
||||
// Only child returned when failure happens : When failed with 'Unauthorized' error, prompt for password.
|
||||
if (children.length === 1 && this.hasExpansionError(children)) {
|
||||
if (children[0].errorStatusCode === 401) {
|
||||
const sqlClusterConnection = await session.getSqlClusterConnection();
|
||||
// First prompt for username (defaulting to existing username)
|
||||
let username: string = await this.promptInput(localize('promptUsername', "Please provide the username to connect to HDFS:"), session.sqlClusterConnection.user);
|
||||
let username = await this.prompter.promptSingle<string>(<IQuestion>{
|
||||
type: QuestionTypes.input,
|
||||
name: 'inputPrompt',
|
||||
message: localize('promptUsername', "Please provide the username to connect to HDFS:"),
|
||||
default: sqlClusterConnection.user
|
||||
});
|
||||
// Only update the username if it's different than the original (the update functions ignore falsy values)
|
||||
if (username === session.sqlClusterConnection.user) {
|
||||
if (username === sqlClusterConnection.user) {
|
||||
username = '';
|
||||
}
|
||||
session.sqlClusterConnection.updateUsername(username);
|
||||
sqlClusterConnection.updateUsername(username);
|
||||
|
||||
// And then prompt for password
|
||||
const password: string = await this.promptPassword(localize('prmptPwd', "Please provide the password to connect to HDFS:"));
|
||||
session.sqlClusterConnection.updatePassword(password);
|
||||
const password = await this.prompter.promptSingle<string>(<IQuestion>{
|
||||
type: QuestionTypes.password,
|
||||
name: 'passwordPrompt',
|
||||
message: localize('prmptPwd', "Please provide the password to connect to HDFS:"),
|
||||
default: ''
|
||||
});
|
||||
sqlClusterConnection.updatePassword(password);
|
||||
|
||||
if (username || password) {
|
||||
await node.updateFileSource(session.sqlClusterConnection);
|
||||
await node.updateFileSource(sqlClusterConnection);
|
||||
children = await node.getChildren(true);
|
||||
}
|
||||
}
|
||||
@@ -150,31 +156,13 @@ export class MssqlObjectExplorerNodeProvider extends ProviderBase implements azd
|
||||
this.expandCompleteEmitter.fire(expandResult);
|
||||
}
|
||||
|
||||
private async promptInput(promptMsg: string, defaultValue: string): Promise<string> {
|
||||
return await this.prompter.promptSingle(<IQuestion>{
|
||||
type: QuestionTypes.input,
|
||||
name: 'inputPrompt',
|
||||
message: promptMsg,
|
||||
default: defaultValue
|
||||
}).then(confirmed => <string>confirmed);
|
||||
}
|
||||
|
||||
private async promptPassword(promptMsg: string): Promise<string> {
|
||||
return await this.prompter.promptSingle(<IQuestion>{
|
||||
type: QuestionTypes.password,
|
||||
name: 'passwordPrompt',
|
||||
message: promptMsg,
|
||||
default: ''
|
||||
}).then(confirmed => <string>confirmed);
|
||||
}
|
||||
|
||||
refreshNode(nodeInfo: azdata.ExpandNodeInfo): Thenable<boolean> {
|
||||
// TODO #3815 implement properly
|
||||
return this.expandNode(nodeInfo, true);
|
||||
}
|
||||
|
||||
handleSessionClose(closeSessionInfo: azdata.ObjectExplorerCloseSessionInfo): void {
|
||||
this.sessionMap.delete(closeSessionInfo.sessionId);
|
||||
this.clusterSessionMap.delete(closeSessionInfo.sessionId);
|
||||
}
|
||||
|
||||
findNodes(findNodesInfo: azdata.FindNodesInfo): Thenable<azdata.ObjectExplorerFindNodesResponse> {
|
||||
@@ -242,7 +230,7 @@ export class MssqlObjectExplorerNodeProvider extends ProviderBase implements azd
|
||||
}
|
||||
|
||||
public findSqlClusterSessionBySqlConnProfile(connectionProfile: azdata.IConnectionProfile): SqlClusterSession {
|
||||
for (let session of this.sessionMap.values()) {
|
||||
for (let session of this.clusterSessionMap.values()) {
|
||||
if (session.isMatchedSqlConnection(connectionProfile)) {
|
||||
return session;
|
||||
}
|
||||
@@ -251,11 +239,10 @@ export class MssqlObjectExplorerNodeProvider extends ProviderBase implements azd
|
||||
}
|
||||
}
|
||||
|
||||
class SqlClusterSession {
|
||||
export class SqlClusterSession {
|
||||
private _rootNode: SqlClusterRootNode;
|
||||
|
||||
private _sqlClusterConnection: SqlClusterConnection | undefined = undefined;
|
||||
constructor(
|
||||
private _sqlClusterConnection: SqlClusterConnection,
|
||||
private _sqlSession: azdata.ObjectExplorerSession,
|
||||
private _sqlConnectionProfile: azdata.IConnectionProfile,
|
||||
private _appContext: AppContext,
|
||||
@@ -266,7 +253,13 @@ class SqlClusterSession {
|
||||
this._sqlSession.rootNode.nodePath);
|
||||
}
|
||||
|
||||
public get sqlClusterConnection(): SqlClusterConnection { return this._sqlClusterConnection; }
|
||||
public async getSqlClusterConnection(): Promise<SqlClusterConnection> {
|
||||
if (!this._sqlClusterConnection) {
|
||||
const sqlClusterConnectionParams = await getSqlClusterConnectionParams(this._sqlConnectionProfile);
|
||||
this._sqlClusterConnection = new SqlClusterConnection(sqlClusterConnectionParams);
|
||||
}
|
||||
return this._sqlClusterConnection;
|
||||
}
|
||||
public get sqlSession(): azdata.ObjectExplorerSession { return this._sqlSession; }
|
||||
public get sqlConnectionProfile(): azdata.IConnectionProfile { return this._sqlConnectionProfile; }
|
||||
public get sessionId(): string { return this._sqlSession.sessionId; }
|
||||
@@ -284,7 +277,7 @@ class SqlClusterRootNode extends TreeNode {
|
||||
private _treeDataContext: TreeDataContext,
|
||||
private _nodePathValue: string
|
||||
) {
|
||||
super();
|
||||
super(undefined);
|
||||
}
|
||||
|
||||
public get session(): SqlClusterSession {
|
||||
@@ -304,8 +297,8 @@ class SqlClusterRootNode extends TreeNode {
|
||||
|
||||
private async refreshChildren(): Promise<TreeNode[]> {
|
||||
this._children = [];
|
||||
let fileSource: IFileSource = await this.session.sqlClusterConnection.createHdfsFileSource();
|
||||
let hdfsNode = new ConnectionNode(this._treeDataContext, localize('hdfsFolder', "HDFS"), fileSource);
|
||||
|
||||
let hdfsNode = new ConnectionNode(this._treeDataContext, localize('hdfsFolder', "HDFS"), this.session);
|
||||
hdfsNode.parent = this;
|
||||
this._children.push(hdfsNode);
|
||||
return this._children;
|
||||
|
||||
@@ -13,9 +13,10 @@ type TreeNodePredicate = (node: TreeNode) => boolean;
|
||||
|
||||
export abstract class TreeNode implements ITreeNode {
|
||||
private _parent: TreeNode = undefined;
|
||||
protected fileSource: IFileSource;
|
||||
private _errorStatusCode: number;
|
||||
|
||||
constructor(private _fileSource: IFileSource | undefined) { }
|
||||
|
||||
public get parent(): TreeNode {
|
||||
return this._parent;
|
||||
}
|
||||
@@ -77,8 +78,13 @@ export abstract class TreeNode implements ITreeNode {
|
||||
}
|
||||
|
||||
public async updateFileSource(connection: SqlClusterConnection): Promise<void> {
|
||||
this.fileSource = await connection.createHdfsFileSource();
|
||||
this._fileSource = await connection.createHdfsFileSource();
|
||||
}
|
||||
|
||||
public async getFileSource(): Promise<IFileSource | undefined> {
|
||||
return this._fileSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* The value to use for this node in the node path
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user