Introduce Azure Synapse Analytics and Dedicated SQL Pools azure nodes (#21471)

This commit is contained in:
Cheena Malhotra
2022-12-22 17:41:11 -08:00
committed by GitHub
parent 7b4f31d618
commit 8032d50768
25 changed files with 481 additions and 138 deletions

View File

@@ -288,22 +288,22 @@
},
{
"command": "azure.resource.azureview.refresh",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer|synapseSqlPoolContainer|synapseWorkspaceContainer)$/",
"group": "inline"
},
{
"command": "azure.resource.azureview.refresh",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer)$/",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:account|subscription|databaseContainer|databaseServerContainer|synapseSqlPoolContainer|synapseWorkspaceContainer)$/",
"group": "azurecore"
},
{
"command": "azure.resource.connectsqlserver",
"when": "viewItem == azure.resource.itemType.databaseServer || viewItem == azure.resource.itemType.database || viewItem == azure.resource.itemType.sqlInstance",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:database|databaseServer|synapseSqlPool|synapseWorkspace|sqlInstance)$/",
"group": "inline"
},
{
"command": "azure.resource.connectsqlserver",
"when": "viewItem == azure.resource.itemType.databaseServer || viewItem == azure.resource.itemType.database || viewItem == azure.resource.itemType.sqlInstance",
"when": "viewItem =~ /^azure\\.resource\\.itemType\\.(?:database|databaseServer|synapseSqlPool|synapseWorkspace|sqlInstance)$/",
"group": "azurecore"
},
{

View File

@@ -16,6 +16,8 @@ import * as utils from './utils';
const typesClause = [
azureResource.AzureResourceType.sqlDatabase,
azureResource.AzureResourceType.sqlServer,
azureResource.AzureResourceType.sqlSynapseWorkspace,
azureResource.AzureResourceType.sqlSynapseSqlPool,
azureResource.AzureResourceType.sqlManagedInstance,
azureResource.AzureResourceType.postgresServer,
azureResource.AzureResourceType.azureArcService,

View File

@@ -10,6 +10,10 @@ export enum AzureResourceItemType {
database = 'azure.resource.itemType.database',
databaseServerContainer = 'azure.resource.itemType.databaseServerContainer',
databaseServer = 'azure.resource.itemType.databaseServer',
synapseSqlPoolContainer = 'azure.resource.itemType.synapseSqlPoolContainer',
synapseSqlPool = 'azure.resource.itemType.synapseSqlPool',
synapseWorkspaceContainer = 'azure.resource.itemType.synapseWorkspaceContainer',
synapseWorkspace = 'azure.resource.itemType.synapseWorkspace',
azureDataExplorerContainer = 'azure.resource.itemType.azureDataExplorerContainer',
azureDataExplorer = 'azure.resource.itemType.azureDataExplorer',
sqlInstance = 'azure.resource.itemType.sqlInstance',

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { azureResource } from 'azurecore';
import { logAnalyticsQuery } from '../queryStringConstants';
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
export interface AzureMonitorGraphData extends GraphData {
@@ -15,12 +16,10 @@ export interface AzureMonitorGraphData extends GraphData {
};
}
const instanceQuery = `where type == "${azureResource.AzureResourceType.logAnalytics}"`;
export class AzureMonitorResourceService extends ResourceServiceBase<AzureMonitorGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return instanceQuery;
return logAnalyticsQuery;
}
protected convertResource(resource: AzureMonitorGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -6,6 +6,7 @@
import { ResourceServiceBase, GraphData } from '../../resourceTreeDataProviderBase';
import { azureResource } from 'azurecore';
import { cosmosMongoDbQuery } from '../../queryStringConstants';
interface DbServerGraphData extends GraphData {
@@ -15,12 +16,10 @@ interface DbServerGraphData extends GraphData {
};
}
const serversQuery = `where type == "${azureResource.AzureResourceType.cosmosDbAccount}" and kind == "MongoDB"`;
export class CosmosDbMongoService extends ResourceServiceBase<DbServerGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return serversQuery;
return cosmosMongoDbQuery;
}
protected convertResource(resource: DbServerGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtensionContext } from 'vscode';
import { azureResource } from 'azurecore';

View File

@@ -5,8 +5,8 @@
import { ServiceClientCredentials } from '@azure/ms-rest-js';
import { IAzureResourceService } from '../../interfaces';
import { DbServerGraphData, SynapseWorkspaceGraphData } from '../databaseServer/databaseServerService';
import { synapseWorkspacesQuery, sqlServersQuery } from '../databaseServer/serverQueryStrings';
import { DbServerGraphData } from '../databaseServer/databaseServerService';
import { sqlServerQuery, sqlDatabaseQuery } from '../queryStringConstants';
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
import { queryGraphResources, GraphData } from '../resourceTreeDataProviderBase';
import { AzureAccount, azureResource } from 'azurecore';
@@ -19,69 +19,43 @@ export class AzureResourceDatabaseService implements IAzureResourceService<azure
const databases: azureResource.AzureResourceDatabase[] = [];
const resourceClient = new ResourceGraphClient(credential, { baseUri: account.properties.providerSettings.settings.armResource.endpoint });
// Query servers, synapse workspaces, and databases in parallel (start all promises before waiting on the 1st)
let servers: DbServerGraphData[];
let synapseWorkspaces: SynapseWorkspaceGraphData[];
let combined: (DbServerGraphData | SynapseWorkspaceGraphData)[] = [];
/**
* We need to get the list of servers minus the Synapse Workspaces,
* then we need to make another query to get them.
*
* This is done because the first query provides invalid endpoints for Synapse Workspaces
* While the second one provides them as one of its properties.
*
* They have to be processed in different ways as their structure differs
* in terms of properties. (See databaseServer/databaseServerService.ts for more info)
*
* Queries must be made separately due to union not being recognized by resourceGraph resource calls
*/
let synapseQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, synapseWorkspacesQuery);
let serverQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, sqlServersQuery);
let dbQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, `where type == "${azureResource.AzureResourceType.sqlDatabase}"`);
servers = await serverQueryPromise as DbServerGraphData[];
synapseWorkspaces = await synapseQueryPromise as SynapseWorkspaceGraphData[];
// Query servers and databases in parallel (start all promises before waiting on the 1st)
let serverQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, sqlServerQuery);
let dbQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, sqlDatabaseQuery);
let servers: DbServerGraphData[] = await serverQueryPromise as DbServerGraphData[];
let dbByGraph: DatabaseGraphData[] = await dbQueryPromise as DatabaseGraphData[];
combined = combined.concat(servers).concat(synapseWorkspaces);
// Group servers by resource group, then merge DB results with servers so we
// can get the login name and server fully qualified name to use for connections
let rgMap = new Map<string, (DbServerGraphData | SynapseWorkspaceGraphData)[]>();
combined.forEach(s => {
if ((s as SynapseWorkspaceGraphData).properties.connectivityEndpoints) {
// If the resource is a Synapse Workspace, we need to use the managedResourceGroupName
// (any SQL pools inside will use this instead of the regular resource group associated with the workspace itself).
let serversForRg = rgMap.get((s as SynapseWorkspaceGraphData).properties.managedResourceGroupName) || [];
serversForRg.push(s as SynapseWorkspaceGraphData);
rgMap.set((s as SynapseWorkspaceGraphData).properties.managedResourceGroupName, serversForRg);
} else {
let serversForRg = rgMap.get(s.resourceGroup) || [];
serversForRg.push(s);
rgMap.set(s.resourceGroup, serversForRg);
}
let rgMap = new Map<string, (DbServerGraphData)[]>();
servers.forEach(s => {
let serversForRg = rgMap.get(s.resourceGroup) || [];
serversForRg.push(s);
rgMap.set(s.resourceGroup, serversForRg);
});
// Match database ID. When calling exec [0] is full match, [1] is resource group name, [2] is server name
const svrIdRegExp = new RegExp(`\/subscriptions\/.+\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/(.+)\/databases\/.+`);
const synapseDBRegExp = new RegExp(`\/subscriptions\/.+\/resourceGroups\/(.+)\/providers\/Microsoft\.Synapse\/workspaces\/(.+)\/databases\/.+`);
dbByGraph.forEach(db => {
// Filter master DBs, and for all others find their server to get login info
let serversForRg = rgMap.get(db.resourceGroup);
if (serversForRg && !db.kind.endsWith('system') && svrIdRegExp.test(db.id)) {
const founds = svrIdRegExp.exec(db.id);
if (serversForRg && !db.kind.endsWith('system') && (svrIdRegExp.test(db.id) || synapseDBRegExp.test(db.id))) {
const founds = svrIdRegExp.exec(db.id) ?? synapseDBRegExp.exec(db.id);
if (!founds) {
console.warn(`Could not parse server name from ID ${db.id}`);
return;
}
const serverName = founds[2];
let server = combined.find(s => s.name === serverName);
let server = servers.find(s => s.name === serverName);
if (server) {
databases.push({
name: db.name,
id: db.id,
serverName: server.name,
// Determine if server object is for Synapse Workspace or not and get the needed property from the correct place.
serverFullName: (server as SynapseWorkspaceGraphData).properties.connectivityEndpoints?.sql ?? (server as DbServerGraphData).properties.fullyQualifiedDomainName,
loginName: (server as SynapseWorkspaceGraphData).properties.sqlAdministratorLogin ?? (server as DbServerGraphData).properties.administratorLogin,
serverFullName: server.properties.fullyQualifiedDomainName,
loginName: server.properties.administratorLogin,
subscription: {
id: db.subscriptionId,
name: (subscriptions.find(sub => sub.id === db.subscriptionId))?.name || ''

View File

@@ -6,8 +6,8 @@
import { ServiceClientCredentials } from '@azure/ms-rest-js';
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
import { GraphData, queryGraphResources } from '../resourceTreeDataProviderBase';
import { sqlServerQuery } from '../queryStringConstants';
import { azureResource, AzureAccount } from 'azurecore';
import { sqlServersQuery, synapseWorkspacesQuery } from './serverQueryStrings';
import { IAzureResourceService } from '../../interfaces';
export interface DbServerGraphData extends GraphData {
@@ -17,54 +17,14 @@ export interface DbServerGraphData extends GraphData {
};
}
/**
* Properties returned by the Synapse query are different from the server ones and have to be treated differently.
*/
export interface SynapseWorkspaceGraphData extends GraphData {
properties: {
/**
* SQL connectivity endpoint and other endpoints are found here, instead of fullyQualifiedDomainName.
*/
connectivityEndpoints: { sql: string };
/**
* managedResourceGroupName is the resource group used by any SQL pools inside the workspace
* which is different from the resource group of the workspace itself.
*/
managedResourceGroupName: string;
/**
* administratorLogin is called sqlAdministratorLogin here.
*/
sqlAdministratorLogin: string;
};
}
export class AzureResourceDatabaseServerService implements IAzureResourceService<azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return sqlServersQuery;
}
public async getResources(subscriptions: azureResource.AzureResourceSubscription[], credential: ServiceClientCredentials, account: AzureAccount): Promise<azureResource.AzureResourceDatabaseServer[]> {
const convertedResources: azureResource.AzureResourceDatabaseServer[] = [];
const resourceClient = new ResourceGraphClient(credential, { baseUri: account.properties.providerSettings.settings.armResource.endpoint });
/**
* We need to get the list of servers minus the Synapse Workspaces,
* then we need to make another query to get them.
*
* This is done because the first query provides invalid endpoints for Synapse Workspaces
* While the second one provides them as one of its properties.
*
* They have to be processed in different ways by convertResource as their structure differs
* in terms of properties. (See above)
*
* Queries must be made separately due to union not being recognized by resourceGraph resource calls.
*/
let combinedGraphResources: (DbServerGraphData | SynapseWorkspaceGraphData)[] = [];
let serverGraphResources: DbServerGraphData[] = await queryGraphResources<DbServerGraphData>(resourceClient, subscriptions, this.query);
let synapseGraphResources: SynapseWorkspaceGraphData[] = await queryGraphResources<SynapseWorkspaceGraphData>(resourceClient, subscriptions, synapseWorkspacesQuery);
combinedGraphResources = combinedGraphResources.concat(serverGraphResources).concat(synapseGraphResources);
let serverGraphResources: DbServerGraphData[] = await queryGraphResources<DbServerGraphData>(resourceClient, subscriptions, sqlServerQuery);
const ids = new Set<string>();
combinedGraphResources.forEach((res) => {
serverGraphResources.forEach((res) => {
if (!ids.has(res.id)) {
ids.add(res.id);
res.subscriptionName = subscriptions.find(sub => sub.id === res.subscriptionId)?.name;
@@ -76,14 +36,14 @@ export class AzureResourceDatabaseServerService implements IAzureResourceService
return convertedResources;
}
protected convertResource(resource: DbServerGraphData | SynapseWorkspaceGraphData): azureResource.AzureResourceDatabaseServer {
protected convertResource(resource: DbServerGraphData): azureResource.AzureResourceDatabaseServer {
return {
id: resource.id,
name: resource.name,
// Determine if resource object is for Synapse Workspace or not and get the needed property from the correct place.
fullName: (resource as SynapseWorkspaceGraphData).properties.connectivityEndpoints?.sql ?? (resource as DbServerGraphData).properties.fullyQualifiedDomainName,
loginName: (resource as SynapseWorkspaceGraphData).properties.sqlAdministratorLogin ?? (resource as DbServerGraphData).properties.administratorLogin,
fullName: resource.properties.fullyQualifiedDomainName,
loginName: resource.properties.administratorLogin,
defaultDatabaseName: 'master',
subscription: {
id: resource.subscriptionId,

View File

@@ -1,18 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { azureResource } from 'azurecore';
/**
* Get list of Synapse Workspaces with information such as SQL connection endpoints.
*/
export const synapseWorkspacesQuery = `where type == "microsoft.synapse/workspaces"`;
/**
* Lists all Sql Servers except for Synapse Pool Servers
* (they have different properties and need to be handled separately,
* see databaseServerService.ts for more details)
*/
export const sqlServersQuery = `where type == "${azureResource.AzureResourceType.sqlServer}" and kind != "v12.0,analytics"`;

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { azureResource } from 'azurecore';
import { kustoClusterQuery } from '../queryStringConstants';
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
export interface KustoGraphData extends GraphData {
@@ -14,12 +15,10 @@ export interface KustoGraphData extends GraphData {
};
}
const instanceQuery = `where type == "${azureResource.AzureResourceType.kustoClusters}"`;
export class KustoResourceService extends ResourceServiceBase<KustoGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return instanceQuery;
return kustoClusterQuery;
}
protected convertResource(resource: KustoGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -6,6 +6,7 @@
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
import { azureResource } from 'azurecore';
import { mysqlFlexibleServerQuery } from '../queryStringConstants';
interface DbServerGraphData extends GraphData {
@@ -15,12 +16,10 @@ interface DbServerGraphData extends GraphData {
};
}
const serversQuery = `where type == "${azureResource.AzureResourceType.mysqlFlexibleServer}"`;
export class MysqlFlexibleServerService extends ResourceServiceBase<DbServerGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return serversQuery;
return mysqlFlexibleServerQuery;
}
protected convertResource(resource: DbServerGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -5,6 +5,7 @@
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
import { azureResource } from 'azurecore';
import { postgresArcServerQuery } from '../queryStringConstants';
export interface PostgresArcServerGraphData extends GraphData {
properties: {
@@ -12,12 +13,10 @@ export interface PostgresArcServerGraphData extends GraphData {
};
}
export const serversQuery = `where type == "${azureResource.AzureResourceType.azureArcPostgresServer}"`;
export class PostgresServerArcService extends ResourceServiceBase<PostgresArcServerGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return serversQuery;
return postgresArcServerQuery;
}
protected convertResource(resource: PostgresArcServerGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -6,7 +6,7 @@
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
import { azureResource } from 'azurecore';
import { postgresServerQuery } from '../queryStringConstants';
interface DbServerGraphData extends GraphData {
properties: {
@@ -15,12 +15,10 @@ interface DbServerGraphData extends GraphData {
};
}
const serversQuery = `where type == "${azureResource.AzureResourceType.postgresServer}"`;
export class PostgresServerService extends ResourceServiceBase<DbServerGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return serversQuery;
return postgresServerQuery;
}
protected convertResource(resource: DbServerGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -0,0 +1,72 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { azureResource } from 'azurecore';
/**
* Lists all SQL Databases and Synapse SQL Databases
*/
export const sqlDatabaseQuery = `where type == "${azureResource.AzureResourceType.sqlDatabase}" or type == "${azureResource.AzureResourceType.sqlSynapseSqlDatabase}"`;
/**
* Lists all Synapse Workspaces with information such as SQL connection endpoints.
*/
export const synapseWorkspacesQuery = `where type == "${azureResource.AzureResourceType.sqlSynapseWorkspace}"`;
/**
* Lists all Synapse Dedicated SQL Pools
*/
export const synapseSqlPoolsQuery = `where type == "${azureResource.AzureResourceType.sqlSynapseSqlPool}"`;
/**
* Lists all Sql Servers excluding Synapse Pool Servers
* (they have different properties and need to be handled separately)
*/
export const sqlServerQuery = `where type == "${azureResource.AzureResourceType.sqlServer}" and kind != "v12.0,analytics"`;
/**
* Lists all Azure Arc SQL Managed Instances
*/
export const sqlInstanceArcQuery = `where type == "${azureResource.AzureResourceType.azureArcSqlManagedInstance}"`;
/**
* Lists all Azure SQL Managed Instances
*/
export const sqlInstanceQuery = `where type == "${azureResource.AzureResourceType.sqlManagedInstance}"`;
/**
* Lists all resource containers and resource groups
*/
export const resourceGroupQuery = `ResourceContainers | where type=="${azureResource.AzureResourceType.resourceGroup}"`;
/**
* Lists all postgreSQL servers
*/
export const postgresServerQuery = `where type == "${azureResource.AzureResourceType.postgresServer}"`;
/**
* Lists all Azure Arc PostgreSQL servers
*/
export const postgresArcServerQuery = `where type == "${azureResource.AzureResourceType.azureArcPostgresServer}"`;
/**
* Lists all MySQL Flexible servers
*/
export const mysqlFlexibleServerQuery = `where type == "${azureResource.AzureResourceType.mysqlFlexibleServer}"`;
/**
* Lists all Kusto Clusters
*/
export const kustoClusterQuery = `where type == "${azureResource.AzureResourceType.kustoClusters}"`;
/**
* Lists all Cosmos DB for MongoDB accounts
*/
export const cosmosMongoDbQuery = `where type == "${azureResource.AzureResourceType.cosmosDbAccount}" and kind == "MongoDB"`;
/**
* Lists all Log Analytics workspaces
*/
export const logAnalyticsQuery = `where type == "${azureResource.AzureResourceType.logAnalytics}"`;

View File

@@ -6,11 +6,12 @@
import { DbServerGraphData } from '../databaseServer/databaseServerService';
import { azureResource } from 'azurecore';
import { ResourceServiceBase } from '../resourceTreeDataProviderBase';
import { resourceGroupQuery } from '../queryStringConstants';
export class AzureResourceGroupService extends ResourceServiceBase<DbServerGraphData, azureResource.AzureResourceResourceGroup> {
protected get query(): string {
return `ResourceContainers | where type=="${azureResource.AzureResourceType.resourceGroup}"`;
return resourceGroupQuery;
}
protected convertResource(resource: DbServerGraphData): azureResource.AzureResourceResourceGroup {

View File

@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { azureResource } from 'azurecore';
import { sqlInstanceQuery } from '../queryStringConstants';
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
interface SqlInstanceGraphData extends GraphData {
@@ -13,12 +14,10 @@ interface SqlInstanceGraphData extends GraphData {
};
}
const instanceQuery = `where type == "${azureResource.AzureResourceType.sqlManagedInstance}"`;
export class SqlInstanceResourceService extends ResourceServiceBase<SqlInstanceGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return instanceQuery;
return sqlInstanceQuery;
}
protected convertResource(resource: SqlInstanceGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -5,6 +5,7 @@
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
import { azureResource } from 'azurecore';
import { sqlInstanceArcQuery } from '../queryStringConstants';
export interface SqlInstanceArcGraphData extends GraphData {
properties: {
@@ -13,11 +14,10 @@ export interface SqlInstanceArcGraphData extends GraphData {
};
}
const instanceQuery = `where type == "${azureResource.AzureResourceType.azureArcSqlManagedInstance}"`;
export class SqlInstanceArcResourceService extends ResourceServiceBase<SqlInstanceArcGraphData, azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return instanceQuery;
return sqlInstanceArcQuery;
}
protected convertResource(resource: SqlInstanceArcGraphData): azureResource.AzureResourceDatabaseServer {

View File

@@ -0,0 +1,27 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtensionContext } from 'vscode';
import { azureResource } from 'azurecore';
import { AzureResourceSynapseSqlPoolTreeDataProvider as AzureResourceSynapseSqlPoolTreeDataProvider } from './synapseSqlPoolTreeDataProvider';
import { IAzureResourceService } from '../../interfaces';
export class AzureResourceSynapseSqlPoolProvider implements azureResource.IAzureResourceProvider {
public constructor(
private _synapseSqlPoolService: IAzureResourceService<azureResource.AzureResourceDatabase>,
private _extensionContext: ExtensionContext
) {
}
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
return new AzureResourceSynapseSqlPoolTreeDataProvider(this._synapseSqlPoolService, this._extensionContext);
}
public get providerId(): string {
return 'azure.resource.providers.synapseSqlPool';
}
}

View File

@@ -0,0 +1,72 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ServiceClientCredentials } from '@azure/ms-rest-js';
import { IAzureResourceService } from '../../interfaces';
import { SynapseWorkspaceGraphData } from '../synapseWorkspace/synapseWorkspaceService';
import { synapseWorkspacesQuery, synapseSqlPoolsQuery } from '../queryStringConstants';
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
import { queryGraphResources, GraphData } from '../resourceTreeDataProviderBase';
import { AzureAccount, azureResource } from 'azurecore';
interface SynapseGraphData extends GraphData {
kind: string;
}
export class AzureResourceSynapseService implements IAzureResourceService<azureResource.AzureResourceDatabase> {
public async getResources(subscriptions: azureResource.AzureResourceSubscription[], credential: ServiceClientCredentials, account: AzureAccount): Promise<azureResource.AzureResourceDatabase[]> {
const databases: azureResource.AzureResourceDatabase[] = [];
const resourceClient = new ResourceGraphClient(credential, { baseUri: account.properties.providerSettings.settings.armResource.endpoint });
// Query synapse servers, and databases in parallel (start all promises before waiting on the 1st)
let synapseQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, synapseSqlPoolsQuery);
let synapseWorkspaceQueryPromise = queryGraphResources<GraphData>(resourceClient, subscriptions, synapseWorkspacesQuery);
let synapse = await synapseQueryPromise as SynapseGraphData[];
let synapseWorkspaceByGraph: SynapseWorkspaceGraphData[] = await synapseWorkspaceQueryPromise as SynapseWorkspaceGraphData[];
// Group servers by resource group, then merge DB results with servers so we
// can get the login name and server fully qualified name to use for connections
let rgMap = new Map<string, SynapseWorkspaceGraphData[]>();
synapseWorkspaceByGraph.forEach(s => {
// As the resource is a Synapse Workspace, we need to use the managedResourceGroupName
// (any SQL pools inside will use this instead of the regular resource group associated with the workspace itself).
let serversForRg = rgMap.get(s.properties.managedResourceGroupName) || [];
serversForRg.push(s);
rgMap.set(s.properties.managedResourceGroupName, serversForRg);
});
// Match database ID. When calling exec [0] is full match, [1] is resource group name, [2] is server name
const svrIdRegExp = new RegExp(`\/subscriptions\/.+\/resourceGroups\/(.+)\/providers\/Microsoft\.Synapse\/workspaces\/(.+)\/sqlPools\/.+`);
synapse.forEach(db => {
// Filter master DBs, and for all others find their server to get login info
if (!db.kind.endsWith('system') && svrIdRegExp.test(db.id)) {
const founds = svrIdRegExp.exec(db.id);
if (!founds) {
console.warn(`Could not parse server name from ID ${db.id}`);
return;
}
const serverName = founds[2];
let server = synapseWorkspaceByGraph.find(s => s.name === serverName);
if (server) {
databases.push({
name: db.name,
id: db.id,
serverName: server.name,
serverFullName: server.properties.connectivityEndpoints?.sql,
loginName: server.properties.sqlAdministratorLogin,
subscription: {
id: db.subscriptionId,
name: (subscriptions.find(sub => sub.id === db.subscriptionId))?.name || ''
},
tenant: db.tenantId,
resourceGroup: db.resourceGroup
});
}
}
});
return databases;
}
}

View File

@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TreeItem, ExtensionNodeType } from 'azdata';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceItemType, mssqlProvider } from '../../constants';
import { generateGuid } from '../../utils';
import { IAzureResourceService } from '../../interfaces';
import { ResourceTreeDataProviderBase } from '../resourceTreeDataProviderBase';
import { AzureAccount, azureResource } from 'azurecore';
export class AzureResourceSynapseSqlPoolTreeDataProvider extends ResourceTreeDataProviderBase<azureResource.AzureResourceDatabase> {
private static readonly containerId = 'azure.resource.providers.synapseSqlPool.treeDataProvider.synapseSqlPoolContainer';
private static readonly containerLabel = localize('azure.resource.providers.synapseSqlPool.treeDataProvider.synapseSqlPoolContainerLabel', "Dedicated SQL Pools");
public constructor(
synapseSqlPoolService: IAzureResourceService<azureResource.AzureResourceDatabase>,
private _extensionContext: vscode.ExtensionContext
) {
super(synapseSqlPoolService);
}
protected getTreeItemForResource(synapse: azureResource.AzureResourceDatabase, account: AzureAccount): TreeItem {
return {
id: `synapseWorkspace_${synapse.serverFullName}.synapseSqlPool_${synapse.name}`,
label: this.browseConnectionMode ? `${synapse.serverName}/${synapse.name} (${AzureResourceSynapseSqlPoolTreeDataProvider.containerLabel}, ${synapse.subscription.name})` : `${synapse.name} (${synapse.serverName})`,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_database_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/sql_database.svg')
},
collapsibleState: this.browseConnectionMode ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed,
contextValue: AzureResourceItemType.synapseSqlPool,
payload: {
id: generateGuid(),
connectionName: undefined,
serverName: synapse.serverFullName,
databaseName: synapse.name,
userName: synapse.loginName,
password: '',
authenticationType: '',
savePassword: true,
groupFullName: '',
groupId: '',
providerName: mssqlProvider,
saveProfile: false,
options: {},
azureAccount: account.key.accountId,
azureResourceId: synapse.id,
azureTenantId: synapse.tenant,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: mssqlProvider,
type: ExtensionNodeType.Database
};
}
public async getRootChildren(): Promise<TreeItem[]> {
return [{
id: AzureResourceSynapseSqlPoolTreeDataProvider.containerId,
label: AzureResourceSynapseSqlPoolTreeDataProvider.containerLabel,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
},
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: AzureResourceItemType.synapseSqlPoolContainer
}];
}
}

View File

@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtensionContext } from 'vscode';
import { azureResource } from 'azurecore';
import { AzureResourceSynapseWorkspaceTreeDataProvider } from './synapseWorkspaceTreeDataProvider';
import { IAzureResourceService } from '../../interfaces';
export class AzureResourceSynapseWorkspaceProvider implements azureResource.IAzureResourceProvider {
public constructor(
private _synapseWorkspaceService: IAzureResourceService<azureResource.AzureResourceDatabaseServer>,
private _extensionContext: ExtensionContext
) {
}
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
return new AzureResourceSynapseWorkspaceTreeDataProvider(this._synapseWorkspaceService, this._extensionContext);
}
public get providerId(): string {
return 'azure.resource.providers.synapseWorkspace';
}
}

View File

@@ -0,0 +1,73 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ServiceClientCredentials } from '@azure/ms-rest-js';
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
import { GraphData, queryGraphResources } from '../resourceTreeDataProviderBase';
import { azureResource, AzureAccount } from 'azurecore';
import { IAzureResourceService } from '../../interfaces';
import { synapseWorkspacesQuery } from '../queryStringConstants';
/**
* Properties returned by the Synapse query are different from the server ones and have to be treated differently.
*/
export interface SynapseWorkspaceGraphData extends GraphData {
properties: {
/**
* SQL connectivity endpoint and other endpoints are found here, instead of fullyQualifiedDomainName.
*/
connectivityEndpoints: { sql: string };
/**
* managedResourceGroupName is the resource group used by any SQL pools inside the workspace
* which is different from the resource group of the workspace itself.
*/
managedResourceGroupName: string;
/**
* administratorLogin is called sqlAdministratorLogin here.
*/
sqlAdministratorLogin: string;
};
}
export class AzureResourceSynapseWorkspaceService implements IAzureResourceService<azureResource.AzureResourceDatabaseServer> {
protected get query(): string {
return synapseWorkspacesQuery;
}
public async getResources(subscriptions: azureResource.AzureResourceSubscription[], credential: ServiceClientCredentials, account: AzureAccount): Promise<azureResource.AzureResourceDatabaseServer[]> {
const convertedResources: azureResource.AzureResourceDatabaseServer[] = [];
const resourceClient = new ResourceGraphClient(credential, { baseUri: account.properties.providerSettings.settings.armResource.endpoint });
let serverGraphResources: SynapseWorkspaceGraphData[] = await queryGraphResources<SynapseWorkspaceGraphData>(resourceClient, subscriptions, this.query);
const ids = new Set<string>();
serverGraphResources.forEach((res) => {
if (!ids.has(res.id)) {
ids.add(res.id);
res.subscriptionName = subscriptions.find(sub => sub.id === res.subscriptionId)?.name;
const converted = this.convertResource(res);
convertedResources.push(converted);
}
});
return convertedResources;
}
protected convertResource(resource: SynapseWorkspaceGraphData): azureResource.AzureResourceDatabaseServer {
return {
id: resource.id,
name: resource.name,
fullName: resource.properties.connectivityEndpoints?.sql,
loginName: resource.properties.sqlAdministratorLogin,
defaultDatabaseName: 'master',
subscription: {
id: resource.subscriptionId,
name: resource.subscriptionName || ''
},
tenant: resource.tenantId,
resourceGroup: resource.resourceGroup
};
}
}

View File

@@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtensionNodeType, TreeItem } from 'azdata';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { AzureResourceItemType, mssqlProvider } from '../../constants';
import { generateGuid } from '../../utils';
import { IAzureResourceService } from '../../interfaces';
import { ResourceTreeDataProviderBase } from '../resourceTreeDataProviderBase';
import { AzureAccount, azureResource } from 'azurecore';
export class AzureResourceSynapseWorkspaceTreeDataProvider extends ResourceTreeDataProviderBase<azureResource.AzureResourceDatabaseServer> {
private static readonly containerId = 'azure.resource.providers.synapseWorkspace.treeDataProvider.synapseWorkspaceContainer';
private static readonly containerLabel = localize('azure.resource.providers.synapseWorkspace.treeDataProvider.synapseWorkspaceContainerLabel', "Azure Synapse Analytics");
public constructor(
synapseWorkspaceService: IAzureResourceService<azureResource.AzureResourceDatabaseServer>,
private _extensionContext: vscode.ExtensionContext
) {
super(synapseWorkspaceService);
}
protected getTreeItemForResource(synapseWorkspace: azureResource.AzureResourceDatabaseServer, account: AzureAccount): TreeItem {
return {
id: `synapseWorkspace_${synapseWorkspace.id ?? synapseWorkspace.name}`,
label: this.browseConnectionMode ? `${synapseWorkspace.name} (${AzureResourceSynapseWorkspaceTreeDataProvider.containerLabel}, ${synapseWorkspace.subscription.name})` : synapseWorkspace.name,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg')
},
collapsibleState: this.browseConnectionMode ? vscode.TreeItemCollapsibleState.None : vscode.TreeItemCollapsibleState.Collapsed,
contextValue: AzureResourceItemType.synapseWorkspace,
payload: {
id: generateGuid(),
connectionName: undefined,
serverName: synapseWorkspace.fullName,
databaseName: synapseWorkspace.defaultDatabaseName,
userName: synapseWorkspace.loginName,
password: '',
authenticationType: '',
savePassword: true,
groupFullName: '',
groupId: '',
providerName: mssqlProvider,
saveProfile: false,
options: {},
azureAccount: account.key.accountId,
azureTenantId: synapseWorkspace.tenant,
azureResourceId: synapseWorkspace.id,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: mssqlProvider,
type: ExtensionNodeType.Server
};
}
public async getRootChildren(): Promise<TreeItem[]> {
return [{
id: AzureResourceSynapseWorkspaceTreeDataProvider.containerId,
label: AzureResourceSynapseWorkspaceTreeDataProvider.containerLabel,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
},
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: AzureResourceItemType.synapseWorkspaceContainer
}];
}
}

View File

@@ -337,7 +337,7 @@ declare module 'azurecore' {
export namespace azureResource {
/**
* AzureCore core extension supports following resource types of Azure Resource Graph.
* AzureCore extension supports following resource types of Azure Resource Graph.
* To add more resources, please refer this guide: https://docs.microsoft.com/en-us/azure/governance/resource-graph/reference/supported-tables-resources
*/
export const enum AzureResourceType {
@@ -345,6 +345,9 @@ declare module 'azurecore' {
sqlServer = 'microsoft.sql/servers',
sqlDatabase = 'microsoft.sql/servers/databases',
sqlManagedInstance = 'microsoft.sql/managedinstances',
sqlSynapseWorkspace = 'microsoft.synapse/workspaces', // (Synapse Analytics workspace)
sqlSynapseSqlPool = 'microsoft.synapse/workspaces/sqlpools', // (Dedicated SQL pools)
sqlSynapseSqlDatabase = 'microsoft.synapse/workspaces/sqldatabases', // (Synapse SQL databases)
azureArcSqlManagedInstance = 'microsoft.azuredata/sqlmanagedinstances',
virtualMachines = 'microsoft.compute/virtualmachines',
kustoClusters = 'microsoft.kusto/clusters',
@@ -491,6 +494,7 @@ declare module 'azurecore' {
fullName: string;
defaultDatabaseName: string;
}
export interface BlobContainer extends AzureResource { }
export interface FileShare extends AzureResource { }

View File

@@ -50,6 +50,10 @@ import { AzureResourceGroupService } from './azureResource/providers/resourceGro
import { Logger } from './utils/Logger';
import { ConnectionDialogTreeProvider } from './azureResource/tree/connectionDialogTreeProvider';
import { AzureDataGridProvider } from './azureDataGridProvider';
import { AzureResourceSynapseSqlPoolProvider } from './azureResource/providers/synapseSqlPool/synapseSqlPoolProvider';
import { AzureResourceSynapseWorkspaceProvider } from './azureResource/providers/synapseWorkspace/synapseWorkspaceProvider';
import { AzureResourceSynapseWorkspaceService } from './azureResource/providers/synapseWorkspace/synapseWorkspaceService';
import { AzureResourceSynapseService } from './azureResource/providers/synapseSqlPool/synapseSqlPoolService';
let extensionContext: vscode.ExtensionContext;
@@ -139,6 +143,8 @@ export async function activate(context: vscode.ExtensionContext): Promise<azurec
new AzureMonitorProvider(new AzureMonitorResourceService(), extensionContext),
new AzureResourceDatabaseServerProvider(new AzureResourceDatabaseServerService(), extensionContext),
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), extensionContext),
new AzureResourceSynapseSqlPoolProvider(new AzureResourceSynapseService(), extensionContext),
new AzureResourceSynapseWorkspaceProvider(new AzureResourceSynapseWorkspaceService(), extensionContext),
new SqlInstanceProvider(new SqlInstanceResourceService(), extensionContext),
new PostgresServerProvider(new PostgresServerService(), extensionContext),
new CosmosDbMongoProvider(new CosmosDbMongoService(), extensionContext),