From e0d5cd18b91b9ceede895cff439d640000c29174 Mon Sep 17 00:00:00 2001 From: Vsevolod Kukol Date: Thu, 8 Jun 2023 00:38:35 +0200 Subject: [PATCH] Support for MongoDB clusters / vCore (#22512) * Initial support for MongoDB clusters / vCore * Get cluster connection string from arm * Preserve Azure account for any auth type When a service has been selected through the Azure browser, we want to preserve the Azure account information even if a different authentication has been selected. This allows doing ARM operations using the signed in Azure account for any resources including those with a different login types such as clusters. --- .../azurecore/src/azureResource/constants.ts | 3 ++- .../azurecore/src/azureResource/interfaces.ts | 1 + .../cosmosdb/mongo/cosmosDbMongoService.ts | 15 +++++++++++++-- .../mongo/cosmosDbMongoTreeDataProvider.ts | 11 +++++++---- .../providers/queryStringConstants.ts | 2 +- .../providers/universal/universalService.ts | 2 +- extensions/azurecore/src/azurecore.d.ts | 1 + .../connection/browser/connectionWidget.ts | 10 +++++++--- 8 files changed, 33 insertions(+), 12 deletions(-) diff --git a/extensions/azurecore/src/azureResource/constants.ts b/extensions/azurecore/src/azureResource/constants.ts index 66ad7aa81f..e32fb39960 100644 --- a/extensions/azurecore/src/azureResource/constants.ts +++ b/extensions/azurecore/src/azureResource/constants.ts @@ -20,7 +20,8 @@ export enum AzureResourceItemType { message = 'azure.resource.itemType.message', azureMonitor = 'azure.resource.itemType.azureMonitor', azureMonitorContainer = 'azure.resource.itemType.azureMonitorContainer', - cosmosDBMongoAccount = 'azure.resource.itemType.cosmosDBMongoAccount' + cosmosDBMongoAccount = 'azure.resource.itemType.cosmosDBMongoAccount', + cosmosDBMongoCluster = 'azure.resource.itemType.cosmosDBMongoCluster' } export enum AzureResourceServiceNames { diff --git a/extensions/azurecore/src/azureResource/interfaces.ts b/extensions/azurecore/src/azureResource/interfaces.ts index b12c05b5fb..5b3c9a7786 100644 --- a/extensions/azurecore/src/azureResource/interfaces.ts +++ b/extensions/azurecore/src/azureResource/interfaces.ts @@ -50,6 +50,7 @@ export interface DbServerGraphData extends GraphData { properties: { fullyQualifiedDomainName: string; administratorLogin: string; + connectionString: string; }; } diff --git a/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoService.ts b/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoService.ts index 658594ae73..35dd014959 100644 --- a/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoService.ts +++ b/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoService.ts @@ -10,15 +10,26 @@ import { cosmosMongoDbQuery } from '../../queryStringConstants'; import { DbServerGraphData } from '../../../interfaces'; import { COSMOSDB_MONGO_PROVIDER_ID } from '../../../../constants'; +export interface AzureResourceMongoDatabaseServer extends azureResource.AzureResourceDatabaseServer { + isServer: boolean; +} + export class CosmosDbMongoService extends ResourceServiceBase { public override queryFilter: string = cosmosMongoDbQuery; - public convertServerResource(resource: DbServerGraphData): azureResource.AzureResourceDatabaseServer | undefined { + public convertServerResource(resource: DbServerGraphData): AzureResourceMongoDatabaseServer | undefined { + let host = resource.name; + const isServer = resource.type === azureResource.AzureResourceType.cosmosDbCluster; + if (isServer) { + const url = new URL(resource.properties.connectionString); + host = url.hostname; + } return { id: resource.id, name: resource.name, provider: COSMOSDB_MONGO_PROVIDER_ID, - fullName: resource.properties.fullyQualifiedDomainName, + isServer: isServer, + fullName: host, loginName: resource.properties.administratorLogin, defaultDatabaseName: '', tenant: resource.tenantId, diff --git a/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoTreeDataProvider.ts index 1847cc6b8d..69bb09dd5c 100644 --- a/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoTreeDataProvider.ts +++ b/extensions/azurecore/src/azureResource/providers/cosmosdb/mongo/cosmosDbMongoTreeDataProvider.ts @@ -8,6 +8,7 @@ import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); import { AzureResourceItemType, AzureResourcePrefixes, cosmosDBProvider } from '../../../constants'; +import { AzureResourceMongoDatabaseServer } from './cosmosDbMongoService'; import { generateGuid } from '../../../utils'; import { DbServerGraphData, GraphData } from '../../../interfaces'; import { ResourceTreeDataProviderBase } from '../../resourceTreeDataProviderBase'; @@ -25,7 +26,7 @@ export class CosmosDbMongoTreeDataProvider extends ResourceTreeDataProviderBase< super(databaseServerService); } - public getTreeItemForResource(databaseServer: azureResource.AzureResourceDatabaseServer, account: azdata.Account): azdata.TreeItem { + public getTreeItemForResource(databaseServer: AzureResourceMongoDatabaseServer, account: azdata.Account): azdata.TreeItem { return { id: `${AzureResourcePrefixes.cosmosdb}${account.key.accountId}${databaseServer.id ?? databaseServer.name}`, label: `${databaseServer.name} (CosmosDB Mongo API)`, @@ -38,16 +39,18 @@ export class CosmosDbMongoTreeDataProvider extends ResourceTreeDataProviderBase< payload: { id: generateGuid(), connectionName: databaseServer.name, - serverName: databaseServer.name, + serverName: databaseServer.fullName, userName: databaseServer.loginName, password: '', - authenticationType: azdata.connection.AuthenticationType.AzureMFA, + authenticationType: databaseServer.isServer ? azdata.connection.AuthenticationType.SqlLogin : azdata.connection.AuthenticationType.AzureMFA, savePassword: true, groupFullName: '', groupId: '', providerName: cosmosDBProvider, saveProfile: false, - options: {}, + options: { + isServer: databaseServer.isServer, + }, azureAccount: account.key.accountId, azureTenantId: databaseServer.tenant, azureResourceId: databaseServer.id, diff --git a/extensions/azurecore/src/azureResource/providers/queryStringConstants.ts b/extensions/azurecore/src/azureResource/providers/queryStringConstants.ts index ad77e43780..514f282a53 100644 --- a/extensions/azurecore/src/azureResource/providers/queryStringConstants.ts +++ b/extensions/azurecore/src/azureResource/providers/queryStringConstants.ts @@ -71,7 +71,7 @@ export const kustoClusterQuery = `type == "${azureResource.AzureResourceType.kus /** * Lists all Cosmos DB for MongoDB accounts */ -export const cosmosMongoDbQuery = `type == "${azureResource.AzureResourceType.cosmosDbAccount}" and kind == "${Constants.mongoDbKind}"`; +export const cosmosMongoDbQuery = `(type == "${azureResource.AzureResourceType.cosmosDbAccount}" and kind == "${Constants.mongoDbKind}") or type == "${azureResource.AzureResourceType.cosmosDbCluster}"`; /** * Lists all Log Analytics workspaces diff --git a/extensions/azurecore/src/azureResource/providers/universal/universalService.ts b/extensions/azurecore/src/azureResource/providers/universal/universalService.ts index eb96bf875a..bfaf41cd31 100644 --- a/extensions/azurecore/src/azureResource/providers/universal/universalService.ts +++ b/extensions/azurecore/src/azureResource/providers/universal/universalService.ts @@ -79,7 +79,7 @@ export class AzureResourceUniversalService implements azureResource.IAzureResour public getProviderFromResourceType(type: string, kind?: string): [provider: azureResource.IAzureResourceTreeDataProvider, category: ResourceCategory] { - if (type === azureResource.AzureResourceType.cosmosDbAccount && kind === mongoDbKind) { + if ((type === azureResource.AzureResourceType.cosmosDbAccount && kind === mongoDbKind) || type === azureResource.AzureResourceType.cosmosDbCluster) { return [this.getRegisteredTreeDataProviderInstance(COSMOSDB_MONGO_PROVIDER_ID), ResourceCategory.Server]; } else if (type === azureResource.AzureResourceType.sqlDatabase || type === azureResource.AzureResourceType.sqlSynapseSqlDatabase) { return [this.getRegisteredTreeDataProviderInstance(DATABASE_PROVIDER_ID), ResourceCategory.Database]; diff --git a/extensions/azurecore/src/azurecore.d.ts b/extensions/azurecore/src/azurecore.d.ts index 038ad11ef1..6712112b95 100644 --- a/extensions/azurecore/src/azurecore.d.ts +++ b/extensions/azurecore/src/azurecore.d.ts @@ -383,6 +383,7 @@ declare module 'azurecore' { storageAccount = 'microsoft.storage/storageaccounts', logAnalytics = 'microsoft.operationalinsights/workspaces', cosmosDbAccount = 'microsoft.documentdb/databaseaccounts', + cosmosDbCluster = 'microsoft.documentdb/mongoclusters', mysqlFlexibleServer = 'microsoft.dbformysql/flexibleservers' } diff --git a/src/sql/workbench/services/connection/browser/connectionWidget.ts b/src/sql/workbench/services/connection/browser/connectionWidget.ts index 8068f2f141..dff1752740 100644 --- a/src/sql/workbench/services/connection/browser/connectionWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionWidget.ts @@ -974,9 +974,9 @@ export class ConnectionWidget extends lifecycle.Disposable { }); } - if (this.authType === AuthenticationType.AzureMFA || this.authType === AuthenticationType.AzureMFAAndUser) { + if (this.authType === AuthenticationType.AzureMFA || this.authType === AuthenticationType.AzureMFAAndUser || connectionInfo.azureAccount !== null) { this.fillInAzureAccountOptions().then(async () => { - let accountName = (this.authType === AuthenticationType.AzureMFA) + let accountName = ((this.authType === AuthenticationType.AzureMFA) || connectionInfo.azureAccount !== null) ? connectionInfo.azureAccount : connectionInfo.userName; let account: azdata.Account; if (accountName) { @@ -1262,7 +1262,11 @@ export class ConnectionWidget extends lifecycle.Disposable { model.userName = this.userName; model.password = this.password; model.authenticationType = this.authenticationType; - model.azureAccount = this.authToken; + const azureAccount = this.authToken; + if (azureAccount) { + // set the azureAccount only if one has been selected, otherwise preserve the initial model value + model.azureAccount = azureAccount; + } model.savePassword = this._rememberPasswordCheckBox.checked; model.databaseName = this.databaseName; if (this._customOptionWidgets) {