mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-13 19:48:37 -05:00
Add acquireLoginSession API for Azdata (#13985)
* wip * fix tests * add tests * PR comments
This commit is contained in:
@@ -13,6 +13,7 @@ import { getPlatformDownloadLink, getPlatformReleaseVersion } from './azdataRele
|
||||
import { executeCommand, executeSudoCommand, ExitCodeError, ProcessOutput } from './common/childProcess';
|
||||
import { HttpClient } from './common/httpClient';
|
||||
import Logger from './common/logger';
|
||||
import { Deferred } from './common/promise';
|
||||
import { getErrorMessage, NoAzdataError, searchForCmd } from './common/utils';
|
||||
import { azdataAcceptEulaKey, azdataConfigSection, azdataFound, azdataInstallKey, azdataUpdateKey, debugConfigKey, eulaAccepted, eulaUrl, microsoftPrivacyStatementUrl } from './constants';
|
||||
import * as loc from './localizedConstants';
|
||||
@@ -34,12 +35,29 @@ export interface IAzdataTool extends azdataExt.IAzdataApi {
|
||||
executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<R>>
|
||||
}
|
||||
|
||||
class AzdataSession implements azdataExt.AzdataSession {
|
||||
|
||||
private _session = new Deferred<void>();
|
||||
|
||||
public sessionEnded(): Promise<void> {
|
||||
return this._session.promise;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._session.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An object to interact with the azdata tool installed on the box.
|
||||
*/
|
||||
export class AzdataTool implements azdataExt.IAzdataApi {
|
||||
|
||||
private _semVersion: SemVer;
|
||||
private _currentSession: azdataExt.AzdataSession | undefined = undefined;
|
||||
private _currentlyExecutingCommands: Deferred<void>[] = [];
|
||||
private _queuedCommands: { deferred: Deferred<void>, session?: azdataExt.AzdataSession }[] = [];
|
||||
|
||||
constructor(private _path: string, version: string) {
|
||||
this._semVersion = new SemVer(version);
|
||||
}
|
||||
@@ -62,7 +80,17 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
||||
|
||||
public arc = {
|
||||
dc: {
|
||||
create: (namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
create: (
|
||||
namespace: string,
|
||||
name: string,
|
||||
connectivityMode: string,
|
||||
resourceGroup: string,
|
||||
location: string,
|
||||
subscription: string,
|
||||
profileName?: string,
|
||||
storageClass?: string,
|
||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||
session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
const args = ['arc', 'dc', 'create',
|
||||
'--namespace', namespace,
|
||||
'--name', name,
|
||||
@@ -76,32 +104,32 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
||||
if (storageClass) {
|
||||
args.push('--storage-class', storageClass);
|
||||
}
|
||||
return this.executeCommand<void>(args, additionalEnvVars);
|
||||
return this.executeCommand<void>(args, additionalEnvVars, session);
|
||||
},
|
||||
endpoint: {
|
||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<azdataExt.DcEndpointListResult[]>> => {
|
||||
return this.executeCommand<azdataExt.DcEndpointListResult[]>(['arc', 'dc', 'endpoint', 'list'], additionalEnvVars);
|
||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.DcEndpointListResult[]>> => {
|
||||
return this.executeCommand<azdataExt.DcEndpointListResult[]>(['arc', 'dc', 'endpoint', 'list'], additionalEnvVars, session);
|
||||
}
|
||||
},
|
||||
config: {
|
||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigListResult[]>> => {
|
||||
return this.executeCommand<azdataExt.DcConfigListResult[]>(['arc', 'dc', 'config', 'list'], additionalEnvVars);
|
||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigListResult[]>> => {
|
||||
return this.executeCommand<azdataExt.DcConfigListResult[]>(['arc', 'dc', 'config', 'list'], additionalEnvVars, session);
|
||||
},
|
||||
show: (additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigShowResult>> => {
|
||||
return this.executeCommand<azdataExt.DcConfigShowResult>(['arc', 'dc', 'config', 'show'], additionalEnvVars);
|
||||
show: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.DcConfigShowResult>> => {
|
||||
return this.executeCommand<azdataExt.DcConfigShowResult>(['arc', 'dc', 'config', 'show'], additionalEnvVars, session);
|
||||
}
|
||||
}
|
||||
},
|
||||
postgres: {
|
||||
server: {
|
||||
delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
return this.executeCommand<void>(['arc', 'postgres', 'server', 'delete', '-n', name, '--force'], additionalEnvVars);
|
||||
delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
return this.executeCommand<void>(['arc', 'postgres', 'server', 'delete', '-n', name, '--force'], additionalEnvVars, session);
|
||||
},
|
||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> => {
|
||||
return this.executeCommand<azdataExt.PostgresServerListResult[]>(['arc', 'postgres', 'server', 'list'], additionalEnvVars);
|
||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerListResult[]>> => {
|
||||
return this.executeCommand<azdataExt.PostgresServerListResult[]>(['arc', 'postgres', 'server', 'list'], additionalEnvVars, session);
|
||||
},
|
||||
show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> => {
|
||||
return this.executeCommand<azdataExt.PostgresServerShowResult>(['arc', 'postgres', 'server', 'show', '-n', name], additionalEnvVars);
|
||||
show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.PostgresServerShowResult>> => {
|
||||
return this.executeCommand<azdataExt.PostgresServerShowResult>(['arc', 'postgres', 'server', 'show', '-n', name], additionalEnvVars, session);
|
||||
},
|
||||
edit: (
|
||||
name: string,
|
||||
@@ -119,7 +147,8 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
||||
workers?: number
|
||||
},
|
||||
engineVersion?: string,
|
||||
additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||
session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
const argsArray = ['arc', 'postgres', 'server', 'edit', '-n', name];
|
||||
if (args.adminPassword) { argsArray.push('--admin-password'); }
|
||||
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
|
||||
@@ -133,20 +162,20 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
||||
if (args.replaceEngineSettings) { argsArray.push('--replace-engine-settings'); }
|
||||
if (args.workers) { argsArray.push('--workers', args.workers.toString()); }
|
||||
if (engineVersion) { argsArray.push('--engine-version', engineVersion); }
|
||||
return this.executeCommand<void>(argsArray, additionalEnvVars);
|
||||
return this.executeCommand<void>(argsArray, additionalEnvVars, session);
|
||||
}
|
||||
}
|
||||
},
|
||||
sql: {
|
||||
mi: {
|
||||
delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
return this.executeCommand<void>(['arc', 'sql', 'mi', 'delete', '-n', name], additionalEnvVars);
|
||||
delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
return this.executeCommand<void>(['arc', 'sql', 'mi', 'delete', '-n', name], additionalEnvVars, session);
|
||||
},
|
||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> => {
|
||||
return this.executeCommand<azdataExt.SqlMiListResult[]>(['arc', 'sql', 'mi', 'list'], additionalEnvVars);
|
||||
list: (additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiListResult[]>> => {
|
||||
return this.executeCommand<azdataExt.SqlMiListResult[]>(['arc', 'sql', 'mi', 'list'], additionalEnvVars, session);
|
||||
},
|
||||
show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> => {
|
||||
return this.executeCommand<azdataExt.SqlMiShowResult>(['arc', 'sql', 'mi', 'show', '-n', name], additionalEnvVars);
|
||||
show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<azdataExt.SqlMiShowResult>> => {
|
||||
return this.executeCommand<azdataExt.SqlMiShowResult>(['arc', 'sql', 'mi', 'show', '-n', name], additionalEnvVars, session);
|
||||
},
|
||||
edit: (
|
||||
name: string,
|
||||
@@ -157,7 +186,8 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
||||
memoryRequest?: string,
|
||||
noWait?: boolean,
|
||||
},
|
||||
additionalEnvVars?: azdataExt.AdditionalEnvVars
|
||||
additionalEnvVars?: azdataExt.AdditionalEnvVars,
|
||||
session?: azdataExt.AzdataSession
|
||||
): Promise<azdataExt.AzdataOutput<void>> => {
|
||||
const argsArray = ['arc', 'sql', 'mi', 'edit', '-n', name];
|
||||
if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); }
|
||||
@@ -165,14 +195,59 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
||||
if (args.memoryLimit) { argsArray.push('--memory-limit', args.memoryLimit); }
|
||||
if (args.memoryRequest) { argsArray.push('--memory-request', args.memoryRequest); }
|
||||
if (args.noWait) { argsArray.push('--no-wait'); }
|
||||
return this.executeCommand<void>(argsArray, additionalEnvVars);
|
||||
return this.executeCommand<void>(argsArray, additionalEnvVars, session);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public login(endpoint: string, username: string, password: string, additionalEnvVars: azdataExt.AdditionalEnvVars = {}): Promise<azdataExt.AzdataOutput<void>> {
|
||||
return this.executeCommand<void>(['login', '-e', endpoint, '-u', username], Object.assign({}, additionalEnvVars, { 'AZDATA_PASSWORD': password }));
|
||||
public async login(endpoint: string, username: string, password: string, additionalEnvVars: azdataExt.AdditionalEnvVars = {}): Promise<azdataExt.AzdataOutput<void>> {
|
||||
// Since login changes the context we want to wait until all currently executing commands are finished before this is executed
|
||||
while (this._currentlyExecutingCommands.length > 0) {
|
||||
await this._currentlyExecutingCommands[0];
|
||||
}
|
||||
// Logins need to be done outside the session aware logic so call impl directly
|
||||
return this.executeCommandImpl<void>(['login', '-e', endpoint, '-u', username], Object.assign({}, additionalEnvVars, { 'AZDATA_PASSWORD': password }));
|
||||
}
|
||||
|
||||
public async acquireSession(endpoint: string, username: string, password: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataSession> {
|
||||
const session = new AzdataSession();
|
||||
session.sessionEnded().then(async () => {
|
||||
// Wait for all commands running for this session to end
|
||||
while (this._currentlyExecutingCommands.length > 0) {
|
||||
await this._currentlyExecutingCommands[0].promise;
|
||||
}
|
||||
this._currentSession = undefined;
|
||||
// Start our next command now that we're all done with this session
|
||||
// TODO: Should we check if the command has a session that hasn't started? That should never happen..
|
||||
// TODO: Look into kicking off multiple commands
|
||||
this._queuedCommands.shift()?.deferred.resolve();
|
||||
});
|
||||
|
||||
// We're not in a session or waiting on anything so just set the current session right now
|
||||
if (!this._currentSession && this._queuedCommands.length === 0) {
|
||||
this._currentSession = session;
|
||||
} else {
|
||||
// We're in a session or another command is executing so add this to the end of the queued commands and wait our turn
|
||||
const deferred = new Deferred<void>();
|
||||
deferred.promise.then(() => {
|
||||
this._currentSession = session;
|
||||
// We've started a new session so look at all our queued commands and start
|
||||
// the ones for this session now.
|
||||
this._queuedCommands = this._queuedCommands.filter(c => {
|
||||
if (c.session === this._currentSession) {
|
||||
c.deferred.resolve();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
this._queuedCommands.push({ deferred, session: undefined });
|
||||
await deferred.promise;
|
||||
}
|
||||
|
||||
await this.login(endpoint, username, password, additionalEnvVars);
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,7 +265,33 @@ export class AzdataTool implements azdataExt.IAzdataApi {
|
||||
};
|
||||
}
|
||||
|
||||
public async executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<R>> {
|
||||
public async executeCommand<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars, session?: azdataExt.AzdataSession): Promise<azdataExt.AzdataOutput<R>> {
|
||||
if (this._currentSession && this._currentSession !== session) {
|
||||
const deferred = new Deferred<void>();
|
||||
this._queuedCommands.push({ deferred, session: session });
|
||||
await deferred.promise;
|
||||
}
|
||||
const executingDeferred = new Deferred<void>();
|
||||
this._currentlyExecutingCommands.push(executingDeferred);
|
||||
try {
|
||||
return await this.executeCommandImpl<R>(args, additionalEnvVars);
|
||||
}
|
||||
finally {
|
||||
this._currentlyExecutingCommands = this._currentlyExecutingCommands.filter(c => c !== executingDeferred);
|
||||
executingDeferred.resolve();
|
||||
// If there isn't an active session and we still have queued commands then we have to manually kick off the next one
|
||||
if (this._queuedCommands.length > 0 && !this._currentSession) {
|
||||
this._queuedCommands.shift()?.deferred.resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the specified azdata command. This is NOT session-aware so should only be used for calls that don't care about a session
|
||||
* @param args The args to pass to azdata
|
||||
* @param additionalEnvVars Additional environment variables to set for this execution
|
||||
*/
|
||||
private async executeCommandImpl<R>(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise<azdataExt.AzdataOutput<R>> {
|
||||
try {
|
||||
const output = JSON.parse((await executeAzdataCommand(`"${this._path}"`, args.concat(['--output', 'json']), additionalEnvVars)).stdout);
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user