mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Azure: add PostgresSQL support and refactor to use resource graph (#8046)
* Azure: add PostgresSQL support and refactor to use resource graph - Refactored to use @azure/arm-resourcegraph for all queries - Refactored database lookup to do just 2 queries (all servers, all DBS) instead of waiting serially on 1 query per RG - Added Azure Database for PostgresSQL Servers support in the tree - Removed use of older azure APIs in preference to ones compatible with resource graph - Note: Had to use v1.0 of new subscriptions package because resourcegraph is 2month out of date vs all other packages
This commit is contained in:
@@ -6,11 +6,11 @@
|
||||
|
||||
import { window, QuickPickItem } from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import { TokenCredentials } from 'ms-rest';
|
||||
import { AppContext } from '../appContext';
|
||||
import { TokenCredentials } from '@azure/ms-rest-js';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { AppContext } from '../appContext';
|
||||
import { azureResource } from './azure-resource';
|
||||
import { TreeNode } from './treeNode';
|
||||
import { AzureResourceCredentialError } from './errors';
|
||||
@@ -46,7 +46,11 @@ export function registerAzureResourceCommands(appContext: AppContext, tree: Azur
|
||||
}
|
||||
}
|
||||
|
||||
let selectedSubscriptions = (await subscriptionFilterService.getSelectedSubscriptions(accountNode.account)) || <azureResource.AzureResourceSubscription[]>[];
|
||||
let selectedSubscriptions = await subscriptionFilterService.getSelectedSubscriptions(accountNode.account);
|
||||
if (!selectedSubscriptions) {
|
||||
selectedSubscriptions = [];
|
||||
}
|
||||
|
||||
const selectedSubscriptionIds: string[] = [];
|
||||
if (selectedSubscriptions.length > 0) {
|
||||
selectedSubscriptionIds.push(...selectedSubscriptions.map((subscription) => subscription.id));
|
||||
@@ -67,9 +71,9 @@ export function registerAzureResourceCommands(appContext: AppContext, tree: Azur
|
||||
};
|
||||
}).sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
const selectedSubscriptionQuickPickItems = (await window.showQuickPick(subscriptionQuickPickItems, { canPickMany: true }));
|
||||
const selectedSubscriptionQuickPickItems = await window.showQuickPick(subscriptionQuickPickItems, { canPickMany: true });
|
||||
if (selectedSubscriptionQuickPickItems && selectedSubscriptionQuickPickItems.length > 0) {
|
||||
tree.refresh(node, false);
|
||||
await tree.refresh(node, false);
|
||||
|
||||
selectedSubscriptions = selectedSubscriptionQuickPickItems.map((subscriptionItem) => subscriptionItem.subscription);
|
||||
await subscriptionFilterService.saveSelectedSubscriptions(accountNode.account, selectedSubscriptions);
|
||||
@@ -79,7 +83,7 @@ export function registerAzureResourceCommands(appContext: AppContext, tree: Azur
|
||||
appContext.apiWrapper.registerCommand('azure.resource.refreshall', () => tree.notifyNodeChanged(undefined));
|
||||
|
||||
appContext.apiWrapper.registerCommand('azure.resource.refresh', async (node?: TreeNode) => {
|
||||
tree.refresh(node, true);
|
||||
await tree.refresh(node, true);
|
||||
});
|
||||
|
||||
appContext.apiWrapper.registerCommand('azure.resource.signin', async (node?: TreeNode) => {
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import * as msRest from '@azure/ms-rest-js';
|
||||
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { Account, DidChangeAccountsParams } from 'azdata';
|
||||
import { Event } from 'vscode';
|
||||
|
||||
@@ -13,17 +12,15 @@ import { azureResource } from './azure-resource';
|
||||
|
||||
export interface IAzureResourceAccountService {
|
||||
getAccounts(): Promise<Account[]>;
|
||||
|
||||
readonly onDidChangeAccounts: Event<DidChangeAccountsParams>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceSubscriptionService {
|
||||
getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]>;
|
||||
getSubscriptions(account: Account, credential: msRest.ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]>;
|
||||
}
|
||||
|
||||
export interface IAzureResourceSubscriptionFilterService {
|
||||
getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]>;
|
||||
|
||||
saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -50,7 +47,7 @@ export interface AzureSqlResource {
|
||||
}
|
||||
|
||||
export interface IAzureResourceService<T extends AzureSqlResource> {
|
||||
getResources(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<T[]>;
|
||||
getResources(subscription: azureResource.AzureResourceSubscription, credential: msRest.ServiceClientCredentials): Promise<T[]>;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +57,7 @@ export interface AzureResourceDatabase extends AzureSqlResource {
|
||||
}
|
||||
|
||||
export interface AzureResourceDatabaseServer extends AzureSqlResource {
|
||||
id?: string;
|
||||
fullName: string;
|
||||
defaultDatabaseName: string;
|
||||
}
|
||||
|
||||
@@ -3,35 +3,56 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { SqlManagementClient } from 'azure-arm-sql';
|
||||
|
||||
import { ServiceClientCredentials } from '@azure/ms-rest-js';
|
||||
import { azureResource } from '../../azure-resource';
|
||||
import { IAzureResourceService, AzureResourceDatabase } from '../../interfaces';
|
||||
import { serversQuery, DbServerGraphData } from '../databaseServer/databaseServerService';
|
||||
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
|
||||
import { queryGraphResources, GraphData } from '../resourceTreeDataProviderBase';
|
||||
|
||||
interface DatabaseGraphData extends GraphData {
|
||||
kind: string;
|
||||
}
|
||||
export class AzureResourceDatabaseService implements IAzureResourceService<AzureResourceDatabase> {
|
||||
public async getResources(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabase[]> {
|
||||
const databases: AzureResourceDatabase[] = [];
|
||||
const sqlManagementClient = new SqlManagementClient(credential, subscription.id);
|
||||
const svrs = await sqlManagementClient.servers.list();
|
||||
for (const svr of svrs) {
|
||||
// Extract resource group name from svr.id
|
||||
const svrIdRegExp = new RegExp(`\/subscriptions\/${subscription.id}\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/${svr.name}`);
|
||||
if (!svrIdRegExp.test(svr.id)) {
|
||||
continue;
|
||||
const resourceClient = new ResourceGraphClient(credential);
|
||||
|
||||
// Query servers and databases in parallel (start both promises before waiting on the 1st)
|
||||
let serverQueryPromise = queryGraphResources<GraphData>(resourceClient, subscription.id, serversQuery);
|
||||
let dbQueryPromise = queryGraphResources<GraphData>(resourceClient, subscription.id, 'where type == "microsoft.sql/servers/databases"');
|
||||
let servers: DbServerGraphData[] = await serverQueryPromise as DbServerGraphData[];
|
||||
let dbByGraph: DatabaseGraphData[] = await dbQueryPromise as DatabaseGraphData[];
|
||||
|
||||
// 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[]>();
|
||||
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\/${subscription.id}\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/(.+)\/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);
|
||||
const serverName = founds[2];
|
||||
let server = servers.find(s => s.name === serverName);
|
||||
if (server) {
|
||||
databases.push({
|
||||
name: db.name,
|
||||
serverName: server.name,
|
||||
serverFullName: server.properties.fullyQualifiedDomainName,
|
||||
loginName: server.properties.administratorLogin
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const founds = svrIdRegExp.exec(svr.id);
|
||||
const resouceGroup = founds[1];
|
||||
|
||||
const dbs = await sqlManagementClient.databases.listByServer(resouceGroup, svr.name);
|
||||
dbs.forEach((db) => databases.push({
|
||||
name: db.name,
|
||||
serverName: svr.name,
|
||||
serverFullName: svr.fullyQualifiedDomainName,
|
||||
loginName: svr.administratorLogin
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
return databases;
|
||||
}
|
||||
|
||||
@@ -3,26 +3,33 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { SqlManagementClient } from 'azure-arm-sql';
|
||||
|
||||
import { azureResource } from '../../azure-resource';
|
||||
import { IAzureResourceService, AzureResourceDatabaseServer } from '../../interfaces';
|
||||
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
|
||||
import { AzureResourceDatabaseServer } from '../../interfaces';
|
||||
|
||||
export class AzureResourceDatabaseServerService implements IAzureResourceService<AzureResourceDatabaseServer> {
|
||||
public async getResources(subscription: azureResource.AzureResourceSubscription, credential: ServiceClientCredentials): Promise<AzureResourceDatabaseServer[]> {
|
||||
const databaseServers: AzureResourceDatabaseServer[] = [];
|
||||
|
||||
const sqlManagementClient = new SqlManagementClient(credential, subscription.id);
|
||||
const svrs = await sqlManagementClient.servers.list();
|
||||
export interface DbServerGraphData extends GraphData {
|
||||
properties: {
|
||||
fullyQualifiedDomainName: string;
|
||||
administratorLogin: string;
|
||||
};
|
||||
}
|
||||
|
||||
svrs.forEach((svr) => databaseServers.push({
|
||||
name: svr.name,
|
||||
fullName: svr.fullyQualifiedDomainName,
|
||||
loginName: svr.administratorLogin,
|
||||
export const serversQuery = 'where type == "microsoft.sql/servers"';
|
||||
|
||||
export class AzureResourceDatabaseServerService extends ResourceServiceBase<DbServerGraphData, AzureResourceDatabaseServer> {
|
||||
|
||||
protected get query(): string {
|
||||
return serversQuery;
|
||||
}
|
||||
|
||||
protected convertResource(resource: DbServerGraphData): AzureResourceDatabaseServer {
|
||||
return {
|
||||
id: resource.id,
|
||||
name: resource.name,
|
||||
fullName: resource.properties.fullyQualifiedDomainName,
|
||||
loginName: resource.properties.administratorLogin,
|
||||
defaultDatabaseName: 'master'
|
||||
}));
|
||||
|
||||
return databaseServers;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export class AzureResourceDatabaseServerTreeDataProvider extends ResourceTreeDat
|
||||
|
||||
protected getTreeItemForResource(databaseServer: AzureResourceDatabaseServer): TreeItem {
|
||||
return {
|
||||
id: `databaseServer_${databaseServer.name}`,
|
||||
id: `databaseServer_${databaseServer.id ? databaseServer.id : databaseServer.name}`,
|
||||
label: databaseServer.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
import { azureResource } from '../../azure-resource';
|
||||
import { IAzureResourceService, AzureResourceDatabaseServer } from '../../interfaces';
|
||||
import { PostgresServerTreeDataProvider as PostgresServerTreeDataProvider } from './postgresServerTreeDataProvider';
|
||||
|
||||
export class PostgresServerProvider implements azureResource.IAzureResourceProvider {
|
||||
public constructor(
|
||||
private _databaseServerService: IAzureResourceService<AzureResourceDatabaseServer>,
|
||||
private _apiWrapper: ApiWrapper,
|
||||
private _extensionContext: ExtensionContext
|
||||
) {
|
||||
}
|
||||
|
||||
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
|
||||
return new PostgresServerTreeDataProvider(this._databaseServerService, this._apiWrapper, this._extensionContext);
|
||||
}
|
||||
|
||||
public get providerId(): string {
|
||||
return 'azure.resource.providers.postgresServer';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
|
||||
import { AzureResourceDatabaseServer } from '../../interfaces';
|
||||
|
||||
|
||||
export interface DbServerGraphData extends GraphData {
|
||||
properties: {
|
||||
fullyQualifiedDomainName: string;
|
||||
administratorLogin: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const serversQuery = 'where type == "microsoft.dbforpostgresql/servers"';
|
||||
|
||||
export class PostgresServerService extends ResourceServiceBase<DbServerGraphData, AzureResourceDatabaseServer> {
|
||||
|
||||
protected get query(): string {
|
||||
return serversQuery;
|
||||
}
|
||||
|
||||
protected convertResource(resource: DbServerGraphData): AzureResourceDatabaseServer {
|
||||
return {
|
||||
id: resource.id,
|
||||
name: resource.name,
|
||||
fullName: resource.properties.fullyQualifiedDomainName,
|
||||
loginName: resource.properties.administratorLogin,
|
||||
defaultDatabaseName: 'postgres'
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { TreeItemCollapsibleState, ExtensionContext } from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { AzureResourceItemType } from '../../constants';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
import { generateGuid } from '../../utils';
|
||||
import { IAzureResourceService, AzureResourceDatabaseServer } from '../../interfaces';
|
||||
import { ResourceTreeDataProviderBase } from '../resourceTreeDataProviderBase';
|
||||
import { azureResource } from '../../azure-resource';
|
||||
|
||||
export class PostgresServerTreeDataProvider extends ResourceTreeDataProviderBase<AzureResourceDatabaseServer> {
|
||||
private static readonly containerId = 'azure.resource.providers.databaseServer.treeDataProvider.postgresServerContainer';
|
||||
private static readonly containerLabel = localize('azure.resource.providers.databaseServer.treeDataProvider.postgresServerContainerLabel', "Azure Database for PostgreSQL Servers");
|
||||
|
||||
public constructor(
|
||||
databaseServerService: IAzureResourceService<AzureResourceDatabaseServer>,
|
||||
apiWrapper: ApiWrapper,
|
||||
private _extensionContext: ExtensionContext
|
||||
) {
|
||||
super(databaseServerService, apiWrapper);
|
||||
}
|
||||
|
||||
|
||||
protected getTreeItemForResource(databaseServer: AzureResourceDatabaseServer): TreeItem {
|
||||
return {
|
||||
id: `databaseServer_${databaseServer.id ? databaseServer.id : databaseServer.name}`,
|
||||
label: databaseServer.name,
|
||||
// TODO: should get PGSQL-specific icons (also needed in that extension)
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_server_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/sql_server.svg')
|
||||
},
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServer,
|
||||
payload: {
|
||||
id: generateGuid(),
|
||||
connectionName: undefined,
|
||||
serverName: databaseServer.fullName,
|
||||
databaseName: databaseServer.defaultDatabaseName,
|
||||
userName: `${databaseServer.loginName}@${databaseServer.fullName}`,
|
||||
password: '',
|
||||
authenticationType: 'SqlLogin',
|
||||
savePassword: true,
|
||||
groupFullName: '',
|
||||
groupId: '',
|
||||
providerName: 'PGSQL',
|
||||
saveProfile: false,
|
||||
options: {
|
||||
// Set default for SSL or will get error complaining about it not being set correctly
|
||||
'sslmode': 'require'
|
||||
}
|
||||
},
|
||||
childProvider: 'PGSQL',
|
||||
type: ExtensionNodeType.Server
|
||||
};
|
||||
}
|
||||
|
||||
protected createContainerNode(): azureResource.IAzureResourceNode {
|
||||
return {
|
||||
account: undefined,
|
||||
subscription: undefined,
|
||||
tenantId: undefined,
|
||||
treeItem: {
|
||||
id: PostgresServerTreeDataProvider.containerId,
|
||||
label: PostgresServerTreeDataProvider.containerLabel,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
|
||||
light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
|
||||
},
|
||||
collapsibleState: TreeItemCollapsibleState.Collapsed,
|
||||
contextValue: AzureResourceItemType.databaseServerContainer
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,19 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { AzureResource, TreeItem } from 'azdata';
|
||||
import { TokenCredentials } from 'ms-rest';
|
||||
import * as msRest from '@azure/ms-rest-js';
|
||||
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { ApiWrapper } from '../../apiWrapper';
|
||||
import { IAzureResourceService, AzureSqlResource } from '../interfaces';
|
||||
import { AzureResourceErrorMessageUtil } from '../utils';
|
||||
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
|
||||
|
||||
export abstract class ResourceTreeDataProviderBase<T extends AzureSqlResource> implements azureResource.IAzureResourceTreeDataProvider {
|
||||
|
||||
public constructor(
|
||||
protected _resourceService: IAzureResourceService<T>,
|
||||
protected _apiWrapper: ApiWrapper,
|
||||
protected _apiWrapper: ApiWrapper
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -23,24 +25,94 @@ export abstract class ResourceTreeDataProviderBase<T extends AzureSqlResource> i
|
||||
}
|
||||
|
||||
public async getChildren(element?: azureResource.IAzureResourceNode): Promise<azureResource.IAzureResourceNode[]> {
|
||||
if (!element) {
|
||||
return [this.createContainerNode()];
|
||||
}
|
||||
try {
|
||||
if (!element) {
|
||||
return [this.createContainerNode()];
|
||||
}
|
||||
|
||||
let resources: T[] = await this.getResources(element);
|
||||
|
||||
return resources.map((resource) => <azureResource.IAzureResourceNode>{
|
||||
account: element.account,
|
||||
subscription: element.subscription,
|
||||
tenantId: element.tenantId,
|
||||
treeItem: this.getTreeItemForResource(resource)
|
||||
}).sort((a, b) => a.treeItem.label.localeCompare(b.treeItem.label));
|
||||
} catch (error) {
|
||||
console.log(AzureResourceErrorMessageUtil.getErrorMessage(error));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async getResources(element: azureResource.IAzureResourceNode): Promise<T[]> {
|
||||
const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement);
|
||||
const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType);
|
||||
const credential = new msRest.TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType);
|
||||
|
||||
const resources: T[] = await this._resourceService.getResources(element.subscription, credential) || <T[]>[];
|
||||
|
||||
return resources.map((resource) => <azureResource.IAzureResourceNode>{
|
||||
account: element.account,
|
||||
subscription: element.subscription,
|
||||
tenantId: element.tenantId,
|
||||
treeItem: this.getTreeItemForResource(resource)
|
||||
}).sort((a, b) => a.treeItem.label.localeCompare(b.treeItem.label));
|
||||
return resources;
|
||||
}
|
||||
|
||||
protected abstract getTreeItemForResource(resource: T): TreeItem;
|
||||
|
||||
protected abstract createContainerNode(): azureResource.IAzureResourceNode;
|
||||
}
|
||||
|
||||
export interface GraphData {
|
||||
id: string;
|
||||
name: string;
|
||||
location: string;
|
||||
type: string;
|
||||
resourceGroup: string;
|
||||
}
|
||||
|
||||
|
||||
export async function queryGraphResources<T extends GraphData>(resourceClient: ResourceGraphClient, subId: string, resourceQuery: string): Promise<T[]> {
|
||||
const allResources: T[] = [];
|
||||
let totalProcessed = 0;
|
||||
let doQuery = async (skipToken?: string) => {
|
||||
const response = await resourceClient.resources({
|
||||
subscriptions: [subId],
|
||||
query: resourceQuery,
|
||||
options: {
|
||||
resultFormat: 'objectArray',
|
||||
skipToken: skipToken
|
||||
}
|
||||
});
|
||||
const resources: T[] = response.data;
|
||||
totalProcessed += resources.length;
|
||||
allResources.push(...resources);
|
||||
if (response.skipToken && totalProcessed < response.totalRecords) {
|
||||
await doQuery(response.skipToken);
|
||||
}
|
||||
};
|
||||
await doQuery();
|
||||
return allResources;
|
||||
}
|
||||
|
||||
export abstract class ResourceServiceBase<T extends GraphData, U extends AzureSqlResource> implements IAzureResourceService<U> {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
protected abstract get query(): string;
|
||||
|
||||
public async getResources(subscription: azureResource.AzureResourceSubscription, credential: msRest.ServiceClientCredentials): Promise<U[]> {
|
||||
const convertedResources: U[] = [];
|
||||
const resourceClient = new ResourceGraphClient(credential);
|
||||
let graphResources = await queryGraphResources<T>(resourceClient, subscription.id, this.query);
|
||||
let ids = new Set<string>();
|
||||
graphResources.forEach((res) => {
|
||||
if (!ids.has(res.id)) {
|
||||
ids.add(res.id);
|
||||
let converted = this.convertResource(res);
|
||||
convertedResources.push(converted);
|
||||
}
|
||||
});
|
||||
|
||||
return convertedResources;
|
||||
}
|
||||
|
||||
protected abstract convertResource(resource: T): U;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,25 +3,31 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ServiceClientCredentials as OldSc } from 'ms-rest';
|
||||
import { SqlManagementClient } from 'azure-arm-sql';
|
||||
import { AzureResourceDatabaseServer } from '../../interfaces';
|
||||
import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
|
||||
|
||||
import { azureResource } from '../../azure-resource';
|
||||
import { IAzureResourceService, AzureResourceDatabaseServer } from '../../interfaces';
|
||||
export interface SqlInstanceGraphData extends GraphData {
|
||||
properties: {
|
||||
fullyQualifiedDomainName: string;
|
||||
administratorLogin: string;
|
||||
};
|
||||
}
|
||||
|
||||
export class SqlInstanceResourceService implements IAzureResourceService<AzureResourceDatabaseServer> {
|
||||
public async getResources(subscription: azureResource.AzureResourceSubscription, credential: OldSc): Promise<AzureResourceDatabaseServer[]> {
|
||||
const databaseServers: AzureResourceDatabaseServer[] = [];
|
||||
const sqlManagementClient = new SqlManagementClient(credential, subscription.id);
|
||||
const svrs = await sqlManagementClient.managedInstances.list();
|
||||
const instanceQuery = 'where type == "microsoft.sql/managedinstances"';
|
||||
|
||||
svrs.forEach((svr) => databaseServers.push({
|
||||
name: svr.name,
|
||||
fullName: svr.fullyQualifiedDomainName,
|
||||
loginName: svr.administratorLogin,
|
||||
export class SqlInstanceResourceService extends ResourceServiceBase<SqlInstanceGraphData, AzureResourceDatabaseServer> {
|
||||
|
||||
protected get query(): string {
|
||||
return instanceQuery;
|
||||
}
|
||||
|
||||
protected convertResource(resource: SqlInstanceGraphData): AzureResourceDatabaseServer {
|
||||
return {
|
||||
id: resource.id,
|
||||
name: resource.name,
|
||||
fullName: resource.properties.fullyQualifiedDomainName,
|
||||
loginName: resource.properties.administratorLogin,
|
||||
defaultDatabaseName: 'master'
|
||||
}));
|
||||
|
||||
return databaseServers;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import { azureResource } from '../../azure-resource';
|
||||
|
||||
export class SqlInstanceTreeDataProvider extends ResourceTreeDataProviderBase<AzureResourceDatabaseServer> {
|
||||
private static readonly containerId = 'azure.resource.providers.sqlInstanceContainer';
|
||||
private static readonly containerLabel = localize('azure.resource.providers.sqlInstanceContainerLabel', "SQL Instances");
|
||||
private static readonly containerLabel = localize('azure.resource.providers.sqlInstanceContainerLabel', "SQL Managed Instances");
|
||||
|
||||
public constructor(
|
||||
databaseServerService: IAzureResourceService<AzureResourceDatabaseServer>,
|
||||
@@ -30,7 +30,7 @@ export class SqlInstanceTreeDataProvider extends ResourceTreeDataProviderBase<Az
|
||||
|
||||
protected getTreeItemForResource(databaseServer: AzureResourceDatabaseServer): TreeItem {
|
||||
return {
|
||||
id: `sqlInstance_${databaseServer.name}`,
|
||||
id: `sqlInstance_${databaseServer.id ? databaseServer.id : databaseServer.name}`,
|
||||
label: databaseServer.name,
|
||||
iconPath: {
|
||||
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_instance_inverse.svg'),
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { Account } from 'azdata';
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { SubscriptionClient } from 'azure-arm-resource';
|
||||
import { ServiceClientCredentials } from '@azure/ms-rest-js';
|
||||
import { SubscriptionClient } from '@azure/arm-subscriptions';
|
||||
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { IAzureResourceSubscriptionService } from '../interfaces';
|
||||
@@ -16,7 +14,7 @@ export class AzureResourceSubscriptionService implements IAzureResourceSubscript
|
||||
public async getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
const subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
|
||||
const subClient = new SubscriptionClient.SubscriptionClient(credential);
|
||||
const subClient = new SubscriptionClient(credential);
|
||||
const subs = await subClient.subscriptions.list();
|
||||
subs.forEach((sub) => subscriptions.push({
|
||||
id: sub.subscriptionId,
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
|
||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { Account, NodeInfo, AzureResource } from 'azdata';
|
||||
import { TokenCredentials } from 'ms-rest';
|
||||
import { AppContext } from '../../appContext';
|
||||
import { TokenCredentials } from '@azure/ms-rest-js';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { AppContext } from '../../appContext';
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { TreeNode } from '../treeNode';
|
||||
import { AzureResourceCredentialError } from '../errors';
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
|
||||
import { Account, NodeInfo } from 'azdata';
|
||||
import { AppContext } from '../../appContext';
|
||||
|
||||
@@ -29,6 +29,8 @@ import { registerAzureResourceCommands } from './azureResource/commands';
|
||||
import { AzureResourceTreeProvider } from './azureResource/tree/treeProvider';
|
||||
import { SqlInstanceResourceService } from './azureResource/providers/sqlinstance/sqlInstanceService';
|
||||
import { SqlInstanceProvider } from './azureResource/providers/sqlinstance/sqlInstanceProvider';
|
||||
import { PostgresServerProvider } from './azureResource/providers/postgresServer/postgresServerProvider';
|
||||
import { PostgresServerService } from './azureResource/providers/postgresServer/postgresServerService';
|
||||
|
||||
let extensionContext: vscode.ExtensionContext;
|
||||
|
||||
@@ -65,7 +67,7 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
|
||||
// Create the provider service and activate
|
||||
initAzureAccountProvider(extensionContext, storagePath);
|
||||
initAzureAccountProvider(extensionContext, storagePath).catch((err) => console.log(err));
|
||||
|
||||
registerAzureServices(appContext);
|
||||
const azureResourceTree = new AzureResourceTreeProvider(appContext);
|
||||
@@ -77,7 +79,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
return [
|
||||
new AzureResourceDatabaseServerProvider(new AzureResourceDatabaseServerService(), apiWrapper, extensionContext),
|
||||
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), apiWrapper, extensionContext),
|
||||
new SqlInstanceProvider(new SqlInstanceResourceService(), apiWrapper, extensionContext)
|
||||
new SqlInstanceProvider(new SqlInstanceResourceService(), apiWrapper, extensionContext),
|
||||
new PostgresServerProvider(new PostgresServerService(), apiWrapper, extensionContext)
|
||||
];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as azdata from 'azdata';
|
||||
@@ -179,4 +177,4 @@ describe('AzureResourceService.getTreeItem', function(): void {
|
||||
|
||||
fail();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as azdata from 'azdata';
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as TypeMoq from 'typemoq';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
import { TokenCredentials } from 'ms-rest';
|
||||
import { TokenCredentials } from '@azure/ms-rest-js';
|
||||
import { AppContext } from '../../../appContext';
|
||||
|
||||
import { azureResource } from '../../../azureResource/azure-resource';
|
||||
@@ -343,4 +343,4 @@ describe('AzureResourceAccountTreeNode.clearCache', function (): void {
|
||||
accountTreeNode.clearCache();
|
||||
should(accountTreeNode.isClearingCache).true();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user