Add SQL Managed Instance support and sorting (#7996)

- Add SQL Instances folder and support using existing SQLClient API
- Sort subscriptions in tree and quickpick for easier search
- Sort all resources (Databases, Servers, Instances) alphabetically too

Not in this PR:
- Will experiment with Graph API for faster perf & easier addition of other Azure resources such as PostgreSQL
This commit is contained in:
Kevin Cunnane
2019-10-24 15:18:49 -07:00
committed by GitHub
parent e28ecf44cb
commit 7babd6f3d0
10 changed files with 160 additions and 7 deletions

View File

@@ -65,7 +65,7 @@ export function registerAzureResourceCommands(appContext: AppContext, tree: Azur
picked: selectedSubscriptionIds.indexOf(subscription.id) !== -1,
subscription: subscription
};
});
}).sort((a, b) => a.label.localeCompare(b.label));
const selectedSubscriptionQuickPickItems = (await window.showQuickPick(subscriptionQuickPickItems, { canPickMany: true }));
if (selectedSubscriptionQuickPickItems && selectedSubscriptionQuickPickItems.length > 0) {

View File

@@ -30,14 +30,14 @@ export abstract class ResourceTreeDataProviderBase<T extends AzureSqlResource> i
const tokens = await this._apiWrapper.getSecurityToken(element.account, AzureResource.ResourceManagement);
const credential = new TokenCredentials(tokens[element.tenantId].token, tokens[element.tenantId].tokenType);
const resources: T[] = (await this._resourceService.getResources(element.subscription, credential)) || <T[]>[];
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));
}
protected abstract getTreeItemForResource(resource: T): TreeItem;

View File

@@ -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 { SqlInstanceTreeDataProvider as SqlInstanceTreeDataProvider } from './sqlInstanceTreeDataProvider';
export class SqlInstanceProvider implements azureResource.IAzureResourceProvider {
public constructor(
private _service: IAzureResourceService<AzureResourceDatabaseServer>,
private _apiWrapper: ApiWrapper,
private _extensionContext: ExtensionContext
) {
}
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
return new SqlInstanceTreeDataProvider(this._service, this._apiWrapper, this._extensionContext);
}
public get providerId(): string {
return 'azure.resource.providers.sqlInstance';
}
}

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 { ServiceClientCredentials as OldSc } from 'ms-rest';
import { SqlManagementClient } from 'azure-arm-sql';
import { azureResource } from '../../azure-resource';
import { IAzureResourceService, AzureResourceDatabaseServer } from '../../interfaces';
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();
svrs.forEach((svr) => databaseServers.push({
name: svr.name,
fullName: svr.fullyQualifiedDomainName,
loginName: svr.administratorLogin,
defaultDatabaseName: 'master'
}));
return databaseServers;
}
}

View File

@@ -0,0 +1,78 @@
/*---------------------------------------------------------------------------------------------
* 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 SqlInstanceTreeDataProvider extends ResourceTreeDataProviderBase<AzureResourceDatabaseServer> {
private static readonly containerId = 'azure.resource.providers.sqlInstanceContainer';
private static readonly containerLabel = localize('azure.resource.providers.sqlInstanceContainerLabel', "SQL Instances");
public constructor(
databaseServerService: IAzureResourceService<AzureResourceDatabaseServer>,
apiWrapper: ApiWrapper,
private _extensionContext: ExtensionContext
) {
super(databaseServerService, apiWrapper);
}
protected getTreeItemForResource(databaseServer: AzureResourceDatabaseServer): TreeItem {
return {
id: `sqlInstance_${databaseServer.name}`,
label: databaseServer.name,
iconPath: {
dark: this._extensionContext.asAbsolutePath('resources/dark/sql_instance_inverse.svg'),
light: this._extensionContext.asAbsolutePath('resources/light/sql_instance.svg')
},
collapsibleState: TreeItemCollapsibleState.Collapsed,
contextValue: AzureResourceItemType.databaseServer,
payload: {
id: generateGuid(),
connectionName: undefined,
serverName: databaseServer.fullName,
databaseName: databaseServer.defaultDatabaseName,
userName: databaseServer.loginName,
password: '',
authenticationType: 'SqlLogin',
savePassword: true,
groupFullName: '',
groupId: '',
providerName: 'MSSQL',
saveProfile: false,
options: {}
},
childProvider: 'MSSQL',
type: ExtensionNodeType.Server
};
}
protected createContainerNode(): azureResource.IAzureResourceNode {
return {
account: undefined,
subscription: undefined,
tenantId: undefined,
treeItem: {
id: SqlInstanceTreeDataProvider.containerId,
label: SqlInstanceTreeDataProvider.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
}
};
}
}

View File

@@ -80,11 +80,12 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
if (subscriptions.length === 0) {
return [AzureResourceMessageTreeNode.create(AzureResourceAccountTreeNode.noSubscriptionsLabel, this)];
} else {
return await Promise.all(subscriptions.map(async (subscription) => {
let subTreeNodes = await Promise.all(subscriptions.map(async (subscription) => {
const tenantId = await this._tenantService.getTenantId(subscription);
return new AzureResourceSubscriptionTreeNode(this.account, subscription, tenantId, this.appContext, this.treeChangeHandler, this);
}));
return subTreeNodes.sort((a, b) => a.subscription.name.localeCompare(b.subscription.name));
}
} catch (error) {
if (error instanceof AzureResourceCredentialError) {
@@ -163,4 +164,4 @@ export class AzureResourceAccountTreeNode extends AzureResourceContainerTreeNode
private _selectedSubscriptionCount = 0;
private static readonly noSubscriptionsLabel = localize('azure.resource.tree.accountTreeNode.noSubscriptionsLabel', "No Subscriptions found.");
}
}

View File

@@ -54,7 +54,7 @@ export class AzureResourceSubscriptionTreeNode extends AzureResourceContainerTre
// To make tree node's id unique, otherwise, treeModel.js would complain 'item already registered'
child.resourceNode.treeItem.id = `${this._id}.${child.resourceNode.treeItem.id}`;
return new AzureResourceResourceTreeNode(child, this, this.appContext);
});
}).sort((a, b) => a.nodePathValue.localeCompare(b.nodePathValue));
}
} catch (error) {
return [AzureResourceMessageTreeNode.create(AzureResourceErrorMessageUtil.getErrorMessage(error), this)];