From f314a9e1e630a3e1ccb13e8e5bea8772d1134737 Mon Sep 17 00:00:00 2001 From: Arvind Ranasaria Date: Wed, 23 Dec 2020 23:45:00 -0800 Subject: [PATCH] Change azdata executions to point to kube config and cluster contexts (#13569) --- .../deploy.postgres.existing.arc.ipynb | 3 +- .../deploy.sql.existing.arc.ipynb | 3 +- extensions/arc/package.json | 4 ++ extensions/arc/src/extension.ts | 8 --- extensions/arc/src/localizedConstants.ts | 6 +- extensions/arc/src/models/controllerModel.ts | 46 +++++++++++++-- extensions/arc/src/models/miaaModel.ts | 12 ++-- extensions/arc/src/models/postgresModel.ts | 6 +- extensions/arc/src/models/resourceModel.ts | 4 +- .../arcControllersOptionsSourceProvider.ts | 4 ++ .../arc/src/test/mocks/fakeAzdataApi.ts | 3 +- .../src/test/models/controllerModel.test.ts | 11 ++-- .../ui/tree/azureArcTreeDataProvider.test.ts | 4 +- .../arc/src/ui/components/filePicker.ts | 2 +- .../miaa/miaaComputeAndStoragePage.ts | 1 + .../miaa/miaaDashboardOverviewPage.ts | 5 +- .../postgres/postgresComputeAndStoragePage.ts | 4 +- .../postgres/postgresOverviewPage.ts | 9 ++- .../src/ui/dialogs/connectControllerDialog.ts | 14 ++++- extensions/azdata/src/api.ts | 50 ++++++++-------- extensions/azdata/src/azdata.ts | 58 ++++++++++--------- extensions/azdata/src/common/childProcess.ts | 3 +- .../azdata/src/services/azdataToolService.ts | 4 +- extensions/azdata/src/typings/azdata-ext.d.ts | 30 +++++----- 24 files changed, 180 insertions(+), 114 deletions(-) diff --git a/extensions/arc/notebooks/arcDeployment/deploy.postgres.existing.arc.ipynb b/extensions/arc/notebooks/arcDeployment/deploy.postgres.existing.arc.ipynb index a7547c9eef..e73e612156 100644 --- a/extensions/arc/notebooks/arcDeployment/deploy.postgres.existing.arc.ipynb +++ b/extensions/arc/notebooks/arcDeployment/deploy.postgres.existing.arc.ipynb @@ -2,7 +2,8 @@ "metadata": { "kernelspec": { "name": "python3", - "display_name": "Python 3" + "display_name": "Python 3", + "language": "python" }, "language_info": { "name": "python", diff --git a/extensions/arc/notebooks/arcDeployment/deploy.sql.existing.arc.ipynb b/extensions/arc/notebooks/arcDeployment/deploy.sql.existing.arc.ipynb index 54c3cff9c2..e6d45a14f3 100644 --- a/extensions/arc/notebooks/arcDeployment/deploy.sql.existing.arc.ipynb +++ b/extensions/arc/notebooks/arcDeployment/deploy.sql.existing.arc.ipynb @@ -2,7 +2,8 @@ "metadata": { "kernelspec": { "name": "python3", - "display_name": "Python 3" + "display_name": "Python 3", + "language": "python" }, "language_info": { "name": "python", diff --git a/extensions/arc/package.json b/extensions/arc/package.json index 54c65e9781..dbb9c76031 100644 --- a/extensions/arc/package.json +++ b/extensions/arc/package.json @@ -663,6 +663,8 @@ "variableNames": { "endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT", "username": "AZDATA_NB_VAR_CONTROLLER_USERNAME", + "kubeConfig": "AZDATA_NB_VAR_CONTROLLER_KUBECONFIG", + "clusterContext": "AZDATA_NB_VAR_CONTROLLER_KUBECTL_CONTEXT", "password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD" } }, @@ -855,6 +857,8 @@ "variableNames": { "endpoint": "AZDATA_NB_VAR_CONTROLLER_ENDPOINT", "username": "AZDATA_NB_VAR_CONTROLLER_USERNAME", + "kubeConfig": "AZDATA_NB_VAR_CONTROLLER_KUBECONFIG", + "clusterContext": "AZDATA_NB_VAR_CONTROLLER_KUBECTL_CONTEXT", "password": "AZDATA_NB_VAR_CONTROLLER_PASSWORD" } }, diff --git a/extensions/arc/src/extension.ts b/extensions/arc/src/extension.ts index dffb4ba905..c52e64cbc5 100644 --- a/extensions/arc/src/extension.ts +++ b/extensions/arc/src/extension.ts @@ -28,14 +28,6 @@ export async function activate(context: vscode.ExtensionContext): Promise { - const nodes = await treeDataProvider.getChildren(); - if (nodes.length > 0) { - const response = await vscode.window.showErrorMessage(loc.onlyOneControllerSupported, loc.yes, loc.no); - if (response !== loc.yes) { - return; - } - await treeDataProvider.removeController(nodes[0] as ControllerTreeNode); - } const dialog = new ConnectToControllerDialog(treeDataProvider); dialog.showDialog(); const model = await dialog.waitForClose(); diff --git a/extensions/arc/src/localizedConstants.ts b/extensions/arc/src/localizedConstants.ts index 55185a8c7a..c23e084447 100644 --- a/extensions/arc/src/localizedConstants.ts +++ b/extensions/arc/src/localizedConstants.ts @@ -196,9 +196,8 @@ export function couldNotFindAzureResource(name: string): string { return localiz export function passwordResetFailed(error: any): string { return localize('arc.passwordResetFailed', "Failed to reset password. {0}", getErrorMessage(error)); } export function errorConnectingToController(error: any): string { return localize('arc.errorConnectingToController', "Error connecting to controller. {0}", getErrorMessage(error, true)); } export function passwordAcquisitionFailed(error: any): string { return localize('arc.passwordAcquisitionFailed', "Failed to acquire password. {0}", getErrorMessage(error)); } -export const invalidPassword = localize('arc.invalidPassword', "The password did not work, try again."); +export const loginFailed = localize('arc.loginFailed', "Error logging into controller, try again."); export function errorVerifyingPassword(error: any): string { return localize('arc.errorVerifyingPassword', "Error encountered while verifying password. {0}", getErrorMessage(error)); } -export const onlyOneControllerSupported = localize('arc.onlyOneControllerSupported', "Only one controller connection is currently supported at this time. Do you wish to remove the existing connection and add a new one?"); export const noControllersConnected = localize('noControllersConnected', "No Azure Arc controllers are currently connected. Please run the command: 'Connect to Existing Azure Arc Controller' and then try again"); export const variableValueFetchForUnsupportedVariable = (variableName: string) => localize('getVariableValue.unknownVariableName', "Attempt to get variable value for unknown variable:{0}", variableName); export const isPasswordFetchForUnsupportedVariable = (variableName: string) => localize('getIsPassword.unknownVariableName', "Attempt to get isPassword for unknown variable:{0}", variableName); @@ -211,4 +210,5 @@ export const select = localize('button.label', "Select"); export const noContextFound = (configFile: string) => localize('noContextFound', "No 'contexts' found in the config file: {0}", configFile); export const noCurrentContextFound = (configFile: string) => localize('noCurrentContextFound', "No context is marked as 'current-context' in the config file: {0}", configFile); export const noNameInContext = (configFile: string) => localize('noNameInContext', "No name field was found in a cluster context in the config file: {0}", configFile); -export const userCancelledError = localize('userCancelledError', "User cancelled the dialog"); +export const userCancelledError = localize('arc.userCancelledError', "User cancelled the dialog"); +export const clusterContextConfigNoLongerValid = (configFile: string, clusterContext: string, error: any) => localize('clusterContextConfigNoLongerValid', "The cluster context information specified by config file: {0} and cluster context: {1} is no longer valid. Error is:\n\t{2}\n Do you want to update this information?", configFile, clusterContext, getErrorMessage(error)); diff --git a/extensions/arc/src/models/controllerModel.ts b/extensions/arc/src/models/controllerModel.ts index 8893e16afa..37e0fdb688 100644 --- a/extensions/arc/src/models/controllerModel.ts +++ b/extensions/arc/src/models/controllerModel.ts @@ -7,6 +7,8 @@ import { ControllerInfo, ResourceType } from 'arc'; import * as azdataExt from 'azdata-ext'; import * as vscode from 'vscode'; import { UserCancelledError } from '../common/api'; +import { getCurrentClusterContext, getKubeConfigClusterContexts } from '../common/kubeUtils'; +import { Deferred } from '../common/promise'; import * as loc from '../localizedConstants'; import { ConnectToControllerDialog } from '../ui/dialogs/connectControllerDialog'; import { AzureArcTreeDataProvider } from '../ui/tree/azureArcTreeDataProvider'; @@ -22,6 +24,7 @@ export class ControllerModel { private _endpoints: azdataExt.DcEndpointListResult[] = []; private _registrations: Registration[] = []; private _controllerConfig: azdataExt.DcConfigShowResult | undefined = undefined; + private static _refreshInProgress: Deferred | undefined = undefined; private readonly _onConfigUpdated = new vscode.EventEmitter(); private readonly _onEndpointsUpdated = new vscode.EventEmitter(); @@ -50,19 +53,41 @@ export class ControllerModel { this._onInfoUpdated.fire(this._info); } + public get azdataAdditionalEnvVars(): azdataExt.AdditionalEnvVars { + return { + 'KUBECONFIG': this.info.kubeConfigFilePath, + 'KUBECTL_CONTEXT': this.info.kubeClusterContext + }; + } + /** * Calls azdata login to set the context to this controller * @param promptReconnect */ public async azdataLogin(promptReconnect: boolean = false): Promise { - // We haven't gotten our password yet or we want to prompt for a reconnect - if (!this._password || promptReconnect) { + let promptForValidClusterContext: boolean = false; + try { + const contexts = await getKubeConfigClusterContexts(this.info.kubeConfigFilePath); + getCurrentClusterContext(contexts, this.info.kubeClusterContext, true); // this throws if this.info.kubeClusterContext is not found in 'contexts' + } catch (error) { + const response = await vscode.window.showErrorMessage(loc.clusterContextConfigNoLongerValid(this.info.kubeConfigFilePath, this.info.kubeClusterContext, error), loc.yes, loc.no); + if (response === loc.yes) { + promptForValidClusterContext = true; + } else { + if (!promptReconnect) { //throw unless we are required to prompt for reconnect anyways + throw error; + } + } + } + + // We haven't gotten our password yet or we want to prompt for a reconnect or we want to prompt to reacquire valid cluster context or any and all of these. + if (!this._password || promptReconnect || promptForValidClusterContext) { this._password = ''; if (this.info.rememberPassword) { // It should be in the credentials store, get it from there this._password = await this.treeDataProvider.getPassword(this.info); } - if (promptReconnect || !this._password) { + if (promptReconnect || !this._password || promptForValidClusterContext) { // No password yet or we want to re-prompt for credentials so prompt for it from the user const dialog = new ConnectToControllerDialog(this.treeDataProvider); dialog.showDialog(this.info, this._password); @@ -70,13 +95,14 @@ export class ControllerModel { if (model) { await this.treeDataProvider.addOrUpdateController(model.controllerModel, model.password, false); this._password = model.password; + this._info = model.controllerModel.info; } else { throw new UserCancelledError(loc.userCancelledError); } } } - await this._azdataApi.azdata.login(this.info.url, this.info.username, this._password); + await this._azdataApi.azdata.login(this.info.url, this.info.username, this._password, this.azdataAdditionalEnvVars); } /** @@ -91,6 +117,12 @@ export class ControllerModel { } } public async refresh(showErrors: boolean = true, promptReconnect: boolean = false): Promise { + //wait for any previous refresh that might be in progress to finish + if (ControllerModel._refreshInProgress) { + await ControllerModel._refreshInProgress; + } + // create a new in progress promise object + ControllerModel._refreshInProgress = new Deferred(); await this.azdataLogin(promptReconnect); const newRegistrations: Registration[] = []; await Promise.all([ @@ -108,7 +140,7 @@ export class ControllerModel { this._onConfigUpdated.fire(this._controllerConfig); throw err; }), - this._azdataApi.azdata.arc.dc.endpoint.list().then(result => { + this._azdataApi.azdata.arc.dc.endpoint.list(this.azdataAdditionalEnvVars).then(result => { this._endpoints = result.result; this.endpointsLastUpdated = new Date(); this._onEndpointsUpdated.fire(this._endpoints); @@ -123,7 +155,7 @@ export class ControllerModel { throw err; }), Promise.all([ - this._azdataApi.azdata.arc.postgres.server.list().then(result => { + this._azdataApi.azdata.arc.postgres.server.list(this.azdataAdditionalEnvVars).then(result => { newRegistrations.push(...result.result.map(r => { return { instanceName: r.name, @@ -147,6 +179,8 @@ export class ControllerModel { this._onRegistrationsUpdated.fire(this._registrations); }) ]); + ControllerModel._refreshInProgress.resolve(); + ControllerModel._refreshInProgress = undefined; } public get endpoints(): azdataExt.DcEndpointListResult[] { diff --git a/extensions/arc/src/models/miaaModel.ts b/extensions/arc/src/models/miaaModel.ts index ff3c6cc3d2..77a796d225 100644 --- a/extensions/arc/src/models/miaaModel.ts +++ b/extensions/arc/src/models/miaaModel.ts @@ -38,8 +38,8 @@ export class MiaaModel extends ResourceModel { private _refreshPromise: Deferred | undefined = undefined; - constructor(private _controllerModel: ControllerModel, private _miaaInfo: MiaaResourceInfo, registration: Registration, private _treeDataProvider: AzureArcTreeDataProvider) { - super(_miaaInfo, registration); + constructor(controllerModel: ControllerModel, private _miaaInfo: MiaaResourceInfo, registration: Registration, private _treeDataProvider: AzureArcTreeDataProvider) { + super(controllerModel, _miaaInfo, registration); this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports; } @@ -77,7 +77,7 @@ export class MiaaModel extends ResourceModel { } this._refreshPromise = new Deferred(); try { - await this._controllerModel.azdataLogin(); + await this.controllerModel.azdataLogin(); try { const result = await this._azdataApi.azdata.arc.sql.mi.show(this.info.name); this._config = result.result; @@ -180,7 +180,7 @@ export class MiaaModel extends ResourceModel { if (this.info.connectionId) { try { const credentialProvider = await azdata.credentials.getProvider(credentialNamespace); - const credentials = await credentialProvider.readCredential(createCredentialId(this._controllerModel.info.id, this.info.resourceType, this.info.name)); + const credentials = await credentialProvider.readCredential(createCredentialId(this.controllerModel.info.id, this.info.resourceType, this.info.name)); if (credentials.password) { // Try to connect to verify credentials are still valid connectionProfile.password = credentials.password; @@ -189,7 +189,7 @@ export class MiaaModel extends ResourceModel { const result = await azdata.connection.connect(connectionProfile, false, false); if (!result.connected) { vscode.window.showErrorMessage(loc.connectToSqlFailed(connectionProfile.serverName, result.errorMessage)); - const connectToSqlDialog = new ConnectToSqlDialog(this._controllerModel, this); + const connectToSqlDialog = new ConnectToSqlDialog(this.controllerModel, this); connectToSqlDialog.showDialog(connectionProfile); connectionProfile = await connectToSqlDialog.waitForClose(); } @@ -203,7 +203,7 @@ export class MiaaModel extends ResourceModel { if (!connectionProfile?.userName || !connectionProfile?.password) { // Need to prompt user for password since we don't have one stored - const connectToSqlDialog = new ConnectToSqlDialog(this._controllerModel, this); + const connectToSqlDialog = new ConnectToSqlDialog(this.controllerModel, this); connectToSqlDialog.showDialog(connectionProfile); connectionProfile = await connectToSqlDialog.waitForClose(); } diff --git a/extensions/arc/src/models/postgresModel.ts b/extensions/arc/src/models/postgresModel.ts index e5b08c68cb..429aec686a 100644 --- a/extensions/arc/src/models/postgresModel.ts +++ b/extensions/arc/src/models/postgresModel.ts @@ -22,8 +22,8 @@ export class PostgresModel extends ResourceModel { private _refreshPromise?: Deferred; - constructor(private _controllerModel: ControllerModel, info: ResourceInfo, registration: Registration) { - super(info, registration); + constructor(controllerModel: ControllerModel, info: ResourceInfo, registration: Registration) { + super(controllerModel, info, registration); this._azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports; } @@ -91,7 +91,7 @@ export class PostgresModel extends ResourceModel { this._refreshPromise = new Deferred(); try { - await this._controllerModel.azdataLogin(); + await this.controllerModel.azdataLogin(); this._config = (await this._azdataApi.azdata.arc.postgres.server.show(this.info.name)).result; this.configLastUpdated = new Date(); this._onConfigUpdated.fire(this._config); diff --git a/extensions/arc/src/models/resourceModel.ts b/extensions/arc/src/models/resourceModel.ts index 9698d0af9e..99760732a4 100644 --- a/extensions/arc/src/models/resourceModel.ts +++ b/extensions/arc/src/models/resourceModel.ts @@ -5,14 +5,14 @@ import { ResourceInfo } from 'arc'; import * as vscode from 'vscode'; -import { Registration } from './controllerModel'; +import { ControllerModel, Registration } from './controllerModel'; export abstract class ResourceModel { private readonly _onRegistrationUpdated = new vscode.EventEmitter(); public onRegistrationUpdated = this._onRegistrationUpdated.event; - constructor(public info: ResourceInfo, private _registration: Registration) { } + constructor(public readonly controllerModel: ControllerModel, public info: ResourceInfo, private _registration: Registration) { } public get registration(): Registration { return this._registration; diff --git a/extensions/arc/src/providers/arcControllersOptionsSourceProvider.ts b/extensions/arc/src/providers/arcControllersOptionsSourceProvider.ts index 9ded9ed507..2a50e7cc55 100644 --- a/extensions/arc/src/providers/arcControllersOptionsSourceProvider.ts +++ b/extensions/arc/src/providers/arcControllersOptionsSourceProvider.ts @@ -35,6 +35,8 @@ export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourcePro switch (variableName) { case 'endpoint': return controller.info.url; case 'username': return controller.info.username; + case 'kubeConfig': return controller.info.kubeConfigFilePath; + case 'clusterContext': return controller.info.kubeClusterContext; case 'password': return this.getPassword(controller); default: throw new Error(loc.variableValueFetchForUnsupportedVariable(variableName)); } @@ -59,6 +61,8 @@ export class ArcControllersOptionsSourceProvider implements rd.IOptionsSourcePro switch (variableName) { case 'endpoint': return false; case 'username': return false; + case 'kubeConfig': return false; + case 'clusterContext': return false; case 'password': return true; default: throw new Error(loc.isPasswordFetchForUnsupportedVariable(variableName)); } diff --git a/extensions/arc/src/test/mocks/fakeAzdataApi.ts b/extensions/arc/src/test/mocks/fakeAzdataApi.ts index 42686b7f04..edd0effa47 100644 --- a/extensions/arc/src/test/mocks/fakeAzdataApi.ts +++ b/extensions/arc/src/test/mocks/fakeAzdataApi.ts @@ -50,7 +50,8 @@ export class FakeAzdataApi implements azdataExt.IAzdataApi { workers?: number }, _engineVersion?: string, - _additionalEnvVars?: { [key: string]: string }): Promise> { throw new Error('Method not implemented.'); } + _additionalEnvVars?: azdataExt.AdditionalEnvVars + ): Promise> { throw new Error('Method not implemented.'); } } }, sql: { diff --git a/extensions/arc/src/test/models/controllerModel.test.ts b/extensions/arc/src/test/models/controllerModel.test.ts index 99381fcc73..e8ff3fca4b 100644 --- a/extensions/arc/src/test/models/controllerModel.test.ts +++ b/extensions/arc/src/test/models/controllerModel.test.ts @@ -12,6 +12,7 @@ import * as TypeMoq from 'typemoq'; import { v4 as uuid } from 'uuid'; import * as vscode from 'vscode'; import * as loc from '../../localizedConstants'; +import * as kubeUtils from '../../common/kubeUtils'; import { UserCancelledError } from '../../common/api'; import { ControllerModel } from '../../models/controllerModel'; import { ConnectToControllerDialog } from '../../ui/dialogs/connectControllerDialog'; @@ -34,6 +35,8 @@ describe('ControllerModel', function (): void { beforeEach(function (): void { sinon.stub(ConnectToControllerDialog.prototype, 'showDialog'); + sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').resolves([{ name: 'currentCluster', isCurrentContext: true }]); + sinon.stub(vscode.window, 'showErrorMessage').resolves(loc.yes); }); it('Rejected with expected error when user cancels', async function (): Promise { @@ -61,7 +64,7 @@ describe('ControllerModel', function (): void { const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }); await model.azdataLogin(); - azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once()); + azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once()); }); it('Prompt for password when not in cred store', async function (): Promise { @@ -87,7 +90,7 @@ describe('ControllerModel', function (): void { const model = new ControllerModel(new AzureArcTreeDataProvider(mockExtensionContext.object), { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', username: 'admin', name: 'arc', rememberPassword: true, resources: [] }); await model.azdataLogin(); - azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once()); + azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once()); }); it('Prompt for password when rememberPassword is true but prompt reconnect is true', async function (): Promise { @@ -113,7 +116,7 @@ describe('ControllerModel', function (): void { await model.azdataLogin(true); should(waitForCloseStub.called).be.true('waitForClose should have been called'); - azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once()); + azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once()); }); it('Prompt for password when we already have a password but prompt reconnect is true', async function (): Promise { @@ -140,7 +143,7 @@ describe('ControllerModel', function (): void { await model.azdataLogin(true); should(waitForCloseStub.called).be.true('waitForClose should have been called'); - azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password), TypeMoq.Times.once()); + azdataMock.verify(x => x.login(TypeMoq.It.isAny(), TypeMoq.It.isAny(), password, TypeMoq.It.isAny()), TypeMoq.Times.once()); }); it('Model values are updated correctly when modified during reconnect', async function (): Promise { diff --git a/extensions/arc/src/test/ui/tree/azureArcTreeDataProvider.test.ts b/extensions/arc/src/test/ui/tree/azureArcTreeDataProvider.test.ts index ef6cc14cd2..0ef7f22c90 100644 --- a/extensions/arc/src/test/ui/tree/azureArcTreeDataProvider.test.ts +++ b/extensions/arc/src/test/ui/tree/azureArcTreeDataProvider.test.ts @@ -11,6 +11,7 @@ import * as sinon from 'sinon'; import { v4 as uuid } from 'uuid'; import * as vscode from 'vscode'; import * as azdataExt from 'azdata-ext'; +import * as kubeUtils from '../../../common/kubeUtils'; import { ControllerModel } from '../../../models/controllerModel'; import { MiaaModel } from '../../../models/miaaModel'; import { AzureArcTreeDataProvider } from '../../../ui/tree/azureArcTreeDataProvider'; @@ -102,13 +103,14 @@ describe('AzureArcTreeDataProvider tests', function (): void { mockArcApi.setup(x => x.azdata).returns(() => fakeAzdataApi); sinon.stub(vscode.extensions, 'getExtension').returns(mockArcExtension.object); + sinon.stub(kubeUtils, 'getKubeConfigClusterContexts').resolves([{ name: 'currentCluster', isCurrentContext: true }]); const controllerModel = new ControllerModel(treeDataProvider, { id: uuid(), url: '127.0.0.1', kubeConfigFilePath: '/path/to/.kube/config', kubeClusterContext: 'currentCluster', name: 'my-arc', username: 'sa', rememberPassword: true, resources: [] }, 'mypassword'); await treeDataProvider.addOrUpdateController(controllerModel, ''); const controllerNode = treeDataProvider.getControllerNode(controllerModel); const children = await treeDataProvider.getChildren(controllerNode); should(children.filter(c => c.label === fakeAzdataApi.postgresInstances[0].name).length).equal(1, 'Should have a Postgres child'); should(children.filter(c => c.label === fakeAzdataApi.miaaInstances[0].name).length).equal(1, 'Should have a MIAA child'); - should(children.length).equal(2, 'Should have excatly 2 children'); + should(children.length).equal(2, 'Should have exactly 2 children'); }); }); diff --git a/extensions/arc/src/ui/components/filePicker.ts b/extensions/arc/src/ui/components/filePicker.ts index 793d8b4230..c02828509f 100644 --- a/extensions/arc/src/ui/components/filePicker.ts +++ b/extensions/arc/src/ui/components/filePicker.ts @@ -44,7 +44,7 @@ export class FilePicker implements IReadOnly { }); if (!fileUris || fileUris.length === 0) { - return; // This can happen when a user cancels out. we don't throw and the user just won't be able to move on until they select something. + return; // This can happen when a user cancels out. We don't throw and the user just won't be able to move on until they select something. } const fileUri = fileUris[0]; //we allow the user to select only one file in the dialog this.filePathInputBox.value = fileUri.fsPath; diff --git a/extensions/arc/src/ui/dashboards/miaa/miaaComputeAndStoragePage.ts b/extensions/arc/src/ui/dashboards/miaa/miaaComputeAndStoragePage.ts index ca00119c23..521e126db7 100644 --- a/extensions/arc/src/ui/dashboards/miaa/miaaComputeAndStoragePage.ts +++ b/extensions/arc/src/ui/dashboards/miaa/miaaComputeAndStoragePage.ts @@ -130,6 +130,7 @@ export class MiaaComputeAndStoragePage extends DashboardPage { }, async (_progress, _token): Promise => { try { + await this._miaaModel.controllerModel.azdataLogin(); await this._azdataApi.azdata.arc.sql.mi.edit( this._miaaModel.info.name, this.saveArgs); } catch (err) { diff --git a/extensions/arc/src/ui/dashboards/miaa/miaaDashboardOverviewPage.ts b/extensions/arc/src/ui/dashboards/miaa/miaaDashboardOverviewPage.ts index 4bf3ad19ae..94e47cef0a 100644 --- a/extensions/arc/src/ui/dashboards/miaa/miaaDashboardOverviewPage.ts +++ b/extensions/arc/src/ui/dashboards/miaa/miaaDashboardOverviewPage.ts @@ -205,8 +205,9 @@ export class MiaaDashboardOverviewPage extends DashboardPage { title: loc.deletingInstance(this._miaaModel.info.name), cancellable: false }, - (_progress, _token) => { - return this._azdataApi.azdata.arc.sql.mi.delete(this._miaaModel.info.name); + async (_progress, _token) => { + await this._controllerModel.azdataLogin(); + return await this._azdataApi.azdata.arc.sql.mi.delete(this._miaaModel.info.name); } ); await this._controllerModel.refreshTreeNode(); diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresComputeAndStoragePage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresComputeAndStoragePage.ts index 37c9014d18..f12767eeaa 100644 --- a/extensions/arc/src/ui/dashboards/postgres/postgresComputeAndStoragePage.ts +++ b/extensions/arc/src/ui/dashboards/postgres/postgresComputeAndStoragePage.ts @@ -156,10 +156,12 @@ export class PostgresComputeAndStoragePage extends DashboardPage { }, async (_progress, _token): Promise => { try { + await this._postgresModel.controllerModel.azdataLogin(); await this._azdataApi.azdata.arc.postgres.server.edit( this._postgresModel.info.name, this.saveArgs, - this._postgresModel.engineVersion); + this._postgresModel.engineVersion + ); } catch (err) { // If an error occurs while editing the instance then re-enable the save button since // the edit wasn't successfully applied diff --git a/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts b/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts index 94a1aecb41..52f22eedda 100644 --- a/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts +++ b/extensions/arc/src/ui/dashboards/postgres/postgresOverviewPage.ts @@ -151,6 +151,7 @@ export class PostgresOverviewPage extends DashboardPage { try { const password = await promptAndConfirmPassword(input => !input ? loc.enterANonEmptyPassword : ''); if (password) { + await this._postgresModel.controllerModel.azdataLogin(); await this._azdataApi.azdata.arc.postgres.server.edit( this._postgresModel.info.name, { @@ -158,7 +159,8 @@ export class PostgresOverviewPage extends DashboardPage { noWait: true }, this._postgresModel.engineVersion, - { 'AZDATA_PASSWORD': password }); + { 'AZDATA_PASSWORD': password } + ); vscode.window.showInformationMessage(loc.passwordReset); } } catch (error) { @@ -185,8 +187,9 @@ export class PostgresOverviewPage extends DashboardPage { title: loc.deletingInstance(this._postgresModel.info.name), cancellable: false }, - (_progress, _token) => { - return this._azdataApi.azdata.arc.postgres.server.delete(this._postgresModel.info.name); + async (_progress, _token) => { + await this._postgresModel.controllerModel.azdataLogin(); + return await this._azdataApi.azdata.arc.postgres.server.delete(this._postgresModel.info.name); } ); await this._controllerModel.refreshTreeNode(); diff --git a/extensions/arc/src/ui/dialogs/connectControllerDialog.ts b/extensions/arc/src/ui/dialogs/connectControllerDialog.ts index 2abfaded1a..d76b9f0c82 100644 --- a/extensions/arc/src/ui/dialogs/connectControllerDialog.ts +++ b/extensions/arc/src/ui/dialogs/connectControllerDialog.ts @@ -271,11 +271,19 @@ export class PasswordToControllerDialog extends ControllerDialogBase { } const azdataApi = vscode.extensions.getExtension(azdataExt.extension.name)?.exports; try { - await azdataApi.azdata.login(this.urlInputBox.value!, this.usernameInputBox.value!, this.passwordInputBox.value); + await azdataApi.azdata.login( + this.urlInputBox.value!, + this.usernameInputBox.value!, + this.passwordInputBox.value, + { + 'KUBECONFIG': this.kubeConfigInputBox.value!, + 'KUBECTL_CONTEXT': this.clusterContextRadioGroup.value! + } + ); } catch (e) { if (getErrorMessage(e).match(/Wrong username or password/i)) { this.dialog.message = { - text: loc.invalidPassword, + text: loc.loginFailed, level: azdata.window.MessageLevel.Error }; return false; @@ -299,3 +307,5 @@ export class PasswordToControllerDialog extends ControllerDialogBase { return dialog; } } + + diff --git a/extensions/azdata/src/api.ts b/extensions/azdata/src/api.ts index f0c1a1eaf1..2f35480617 100644 --- a/extensions/azdata/src/api.ts +++ b/extensions/azdata/src/api.ts @@ -45,47 +45,47 @@ export function getAzdataApi(localAzdataDiscovered: Promise { + create: async (namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string, additionalEnvVars?: azdataExt.AdditionalEnvVars) => { await localAzdataDiscovered; throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); - return azdataToolService.localAzdata.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass); + return azdataToolService.localAzdata.arc.dc.create(namespace, name, connectivityMode, resourceGroup, location, subscription, profileName, storageClass, additionalEnvVars); }, endpoint: { - list: async () => { + list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars) => { await localAzdataDiscovered; throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); - return azdataToolService.localAzdata.arc.dc.endpoint.list(); + return azdataToolService.localAzdata.arc.dc.endpoint.list(additionalEnvVars); } }, config: { - list: async () => { + list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars) => { await localAzdataDiscovered; throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); - return azdataToolService.localAzdata.arc.dc.config.list(); + return azdataToolService.localAzdata.arc.dc.config.list(additionalEnvVars); }, - show: async () => { + show: async (additionalEnvVars?: azdataExt.AdditionalEnvVars) => { await localAzdataDiscovered; throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); - return azdataToolService.localAzdata.arc.dc.config.show(); + return azdataToolService.localAzdata.arc.dc.config.show(additionalEnvVars); } } }, postgres: { server: { - delete: async (name: string) => { + delete: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars) => { await localAzdataDiscovered; throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); - return azdataToolService.localAzdata.arc.postgres.server.delete(name); + return azdataToolService.localAzdata.arc.postgres.server.delete(name, additionalEnvVars); }, - list: async () => { + list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars) => { await localAzdataDiscovered; throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); - return azdataToolService.localAzdata.arc.postgres.server.list(); + return azdataToolService.localAzdata.arc.postgres.server.list(additionalEnvVars); }, - show: async (name: string) => { + show: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars) => { await localAzdataDiscovered; throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); - return azdataToolService.localAzdata.arc.postgres.server.show(name); + return azdataToolService.localAzdata.arc.postgres.server.show(name, additionalEnvVars); }, edit: async ( name: string, @@ -112,20 +112,20 @@ export function getAzdataApi(localAzdataDiscovered: Promise { + delete: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars) => { await localAzdataDiscovered; throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); - return azdataToolService.localAzdata.arc.sql.mi.delete(name); + return azdataToolService.localAzdata.arc.sql.mi.delete(name, additionalEnvVars); }, - list: async () => { + list: async (additionalEnvVars?: azdataExt.AdditionalEnvVars) => { await localAzdataDiscovered; throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); - return azdataToolService.localAzdata.arc.sql.mi.list(); + return azdataToolService.localAzdata.arc.sql.mi.list(additionalEnvVars); }, - show: async (name: string) => { + show: async (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars) => { await localAzdataDiscovered; throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); - return azdataToolService.localAzdata.arc.sql.mi.show(name); + return azdataToolService.localAzdata.arc.sql.mi.show(name, additionalEnvVars); }, edit: async ( name: string, @@ -135,10 +135,12 @@ export function getAzdataApi(localAzdataDiscovered: Promise { + }, + additionalEnvVars?: azdataExt.AdditionalEnvVars + ) => { await localAzdataDiscovered; throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); - return azdataToolService.localAzdata.arc.sql.mi.edit(name, args); + return azdataToolService.localAzdata.arc.sql.mi.edit(name, args, additionalEnvVars); } } } @@ -148,9 +150,9 @@ export function getAzdataApi(localAzdataDiscovered: Promise { + login: async (endpoint: string, username: string, password: string, additionalEnvVars?: azdataExt.AdditionalEnvVars) => { throwIfNoAzdataOrEulaNotAccepted(azdataToolService.localAzdata, isEulaAccepted(memento)); - return azdataToolService.localAzdata.login(endpoint, username, password); + return azdataToolService.localAzdata.login(endpoint, username, password, additionalEnvVars); }, getSemVersion: async () => { await localAzdataDiscovered; diff --git a/extensions/azdata/src/azdata.ts b/extensions/azdata/src/azdata.ts index f0416a7b49..c6204f13ee 100644 --- a/extensions/azdata/src/azdata.ts +++ b/extensions/azdata/src/azdata.ts @@ -31,7 +31,7 @@ export interface IAzdataTool extends azdataExt.IAzdataApi { * @param args The args to pass to azdata * @param parseResult A function used to parse out the raw result into the desired shape */ - executeCommand(args: string[], additionalEnvVars?: { [key: string]: string }): Promise> + executeCommand(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> } /** @@ -62,7 +62,7 @@ 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): Promise> => { + create: (namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> => { const args = ['arc', 'dc', 'create', '--namespace', namespace, '--name', name, @@ -76,32 +76,32 @@ export class AzdataTool implements azdataExt.IAzdataApi { if (storageClass) { args.push('--storage-class', storageClass); } - return this.executeCommand(args); + return this.executeCommand(args, additionalEnvVars); }, endpoint: { - list: (): Promise> => { - return this.executeCommand(['arc', 'dc', 'endpoint', 'list']); + list: (additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> => { + return this.executeCommand(['arc', 'dc', 'endpoint', 'list'], additionalEnvVars); } }, config: { - list: (): Promise> => { - return this.executeCommand(['arc', 'dc', 'config', 'list']); + list: (additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> => { + return this.executeCommand(['arc', 'dc', 'config', 'list'], additionalEnvVars); }, - show: (): Promise> => { - return this.executeCommand(['arc', 'dc', 'config', 'show']); + show: (additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> => { + return this.executeCommand(['arc', 'dc', 'config', 'show'], additionalEnvVars); } } }, postgres: { server: { - delete: (name: string): Promise> => { - return this.executeCommand(['arc', 'postgres', 'server', 'delete', '-n', name, '--force']); + delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> => { + return this.executeCommand(['arc', 'postgres', 'server', 'delete', '-n', name, '--force'], additionalEnvVars); }, - list: (): Promise> => { - return this.executeCommand(['arc', 'postgres', 'server', 'list']); + list: (additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> => { + return this.executeCommand(['arc', 'postgres', 'server', 'list'], additionalEnvVars); }, - show: (name: string): Promise> => { - return this.executeCommand(['arc', 'postgres', 'server', 'show', '-n', name]); + show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> => { + return this.executeCommand(['arc', 'postgres', 'server', 'show', '-n', name], additionalEnvVars); }, edit: ( name: string, @@ -119,7 +119,7 @@ export class AzdataTool implements azdataExt.IAzdataApi { workers?: number }, engineVersion?: string, - additionalEnvVars?: { [key: string]: string }): Promise> => { + additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> => { const argsArray = ['arc', 'postgres', 'server', 'edit', '-n', name]; if (args.adminPassword) { argsArray.push('--admin-password'); } if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); } @@ -139,14 +139,14 @@ export class AzdataTool implements azdataExt.IAzdataApi { }, sql: { mi: { - delete: (name: string): Promise> => { - return this.executeCommand(['arc', 'sql', 'mi', 'delete', '-n', name]); + delete: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> => { + return this.executeCommand(['arc', 'sql', 'mi', 'delete', '-n', name], additionalEnvVars); }, - list: (): Promise> => { - return this.executeCommand(['arc', 'sql', 'mi', 'list']); + list: (additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> => { + return this.executeCommand(['arc', 'sql', 'mi', 'list'], additionalEnvVars); }, - show: (name: string): Promise> => { - return this.executeCommand(['arc', 'sql', 'mi', 'show', '-n', name]); + show: (name: string, additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> => { + return this.executeCommand(['arc', 'sql', 'mi', 'show', '-n', name], additionalEnvVars); }, edit: ( name: string, @@ -156,21 +156,23 @@ export class AzdataTool implements azdataExt.IAzdataApi { memoryLimit?: string, memoryRequest?: string, noWait?: boolean, - }): Promise> => { + }, + additionalEnvVars?: azdataExt.AdditionalEnvVars + ): Promise> => { const argsArray = ['arc', 'sql', 'mi', 'edit', '-n', name]; if (args.coresLimit) { argsArray.push('--cores-limit', args.coresLimit); } if (args.coresRequest) { argsArray.push('--cores-request', args.coresRequest); } 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(argsArray); + return this.executeCommand(argsArray, additionalEnvVars); } } } }; - public login(endpoint: string, username: string, password: string): Promise> { - return this.executeCommand(['login', '-e', endpoint, '-u', username], { 'AZDATA_PASSWORD': password }); + public login(endpoint: string, username: string, password: string, additionalEnvVars: azdataExt.AdditionalEnvVars = {}): Promise> { + return this.executeCommand(['login', '-e', endpoint, '-u', username], Object.assign({}, additionalEnvVars, { 'AZDATA_PASSWORD': password })); } /** @@ -188,7 +190,7 @@ export class AzdataTool implements azdataExt.IAzdataApi { }; } - public async executeCommand(args: string[], additionalEnvVars?: { [key: string]: string }): Promise> { + public async executeCommand(args: string[], additionalEnvVars?: azdataExt.AdditionalEnvVars): Promise> { try { const output = JSON.parse((await executeAzdataCommand(`"${this._path}"`, args.concat(['--output', 'json']), additionalEnvVars)).stdout); return { @@ -609,7 +611,7 @@ async function discoverLatestStableAzdataVersionDarwin(): Promise { return new SemVer(azdataPackageVersionInfo.versions.stable); } -async function executeAzdataCommand(command: string, args: string[], additionalEnvVars: { [key: string]: string } = {}): Promise { +async function executeAzdataCommand(command: string, args: string[], additionalEnvVars: azdataExt.AdditionalEnvVars = {}): Promise { additionalEnvVars = Object.assign(additionalEnvVars, { 'ACCEPT_EULA': 'yes' }); const debug = vscode.workspace.getConfiguration(azdataConfigSection).get(debugConfigKey); if (debug) { diff --git a/extensions/azdata/src/common/childProcess.ts b/extensions/azdata/src/common/childProcess.ts index 6362fab434..eec49bae3e 100644 --- a/extensions/azdata/src/common/childProcess.ts +++ b/extensions/azdata/src/common/childProcess.ts @@ -3,6 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { AdditionalEnvVars } from 'azdata-ext'; import * as cp from 'child_process'; import * as sudo from 'sudo-prompt'; import * as loc from '../localizedConstants'; @@ -47,7 +48,7 @@ export type ProcessOutput = { stdout: string, stderr: string }; * @param args Optional args to pass, every arg and arg value must be a separate item in the array * @param additionalEnvVars Additional environment variables to add to the process environment */ -export async function executeCommand(command: string, args: string[], additionalEnvVars?: { [key: string]: string },): Promise { +export async function executeCommand(command: string, args: string[], additionalEnvVars?: AdditionalEnvVars): Promise { return new Promise((resolve, reject) => { Logger.log(loc.executingCommand(command, args)); const stdoutBuffers: Buffer[] = []; diff --git a/extensions/azdata/src/services/azdataToolService.ts b/extensions/azdata/src/services/azdataToolService.ts index d94e86822a..6476715305 100644 --- a/extensions/azdata/src/services/azdataToolService.ts +++ b/extensions/azdata/src/services/azdataToolService.ts @@ -18,9 +18,7 @@ export class AzdataToolService { } /** - * Sets the localAzdata that was last saved - * - * @param memento The memento that stores the localAzdata object + * Sets the localAzdata object to be used for azdata operations */ set localAzdata(azdata: IAzdataTool | undefined) { this._localAzdata = azdata; diff --git a/extensions/azdata/src/typings/azdata-ext.d.ts b/extensions/azdata/src/typings/azdata-ext.d.ts index e031df6161..74d8582e1b 100644 --- a/extensions/azdata/src/typings/azdata-ext.d.ts +++ b/extensions/azdata/src/typings/azdata-ext.d.ts @@ -16,6 +16,8 @@ declare module 'azdata-ext' { name = 'Microsoft.azdata' } + export type AdditionalEnvVars = { [key: string]: string}; + export interface ErrorWithLink extends Error { messageWithLink: string; } @@ -233,20 +235,20 @@ declare module 'azdata-ext' { export interface IAzdataApi { arc: { dc: { - create(namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string): Promise>, + create(namespace: string, name: string, connectivityMode: string, resourceGroup: string, location: string, subscription: string, profileName?: string, storageClass?: string, additionalEnvVars?: AdditionalEnvVars): Promise>, endpoint: { - list(): Promise> + list(additionalEnvVars?: AdditionalEnvVars): Promise> }, config: { - list(): Promise>, - show(): Promise> + list(additionalEnvVars?: AdditionalEnvVars): Promise>, + show(additionalEnvVars?: AdditionalEnvVars): Promise> } }, postgres: { server: { - delete(name: string): Promise>, - list(): Promise>, - show(name: string): Promise>, + delete(name: string, additionalEnvVars?: AdditionalEnvVars): Promise>, + list(additionalEnvVars?: AdditionalEnvVars): Promise>, + show(name: string, additionalEnvVars?: AdditionalEnvVars): Promise>, edit( name: string, args: { @@ -263,14 +265,15 @@ declare module 'azdata-ext' { workers?: number }, engineVersion?: string, - additionalEnvVars?: { [key: string]: string }): Promise> + additionalEnvVars?: AdditionalEnvVars + ): Promise> } }, sql: { mi: { - delete(name: string): Promise>, - list(): Promise>, - show(name: string): Promise>, + delete(name: string, additionalEnvVars?: AdditionalEnvVars): Promise>, + list(additionalEnvVars?: AdditionalEnvVars): Promise>, + show(name: string, additionalEnvVars?: AdditionalEnvVars): Promise>, edit( name: string, args: { @@ -279,13 +282,14 @@ declare module 'azdata-ext' { memoryLimit?: string, memoryRequest?: string, noWait?: boolean, - } + }, + additionalEnvVars?: AdditionalEnvVars ): Promise> } } }, getPath(): Promise, - login(endpoint: string, username: string, password: string): Promise>, + login(endpoint: string, username: string, password: string, additionalEnvVars?: AdditionalEnvVars): Promise>, /** * The semVersion corresponding to this installation of azdata. version() method should have been run * before fetching this value to ensure that correct value is returned. This is almost always correct unless