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

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>SQLManagedInstance_white</title>
<rect x="2.00012" y="2" width="5" height="1" fill="#fff"/>
<rect x="2.00012" y="4" width="5" height="1" fill="#fff"/>
<path d="M.46039,8.4613a4.91729,4.91729,0,0,1,.53973-.44452V1h7V5.031C8.16608,5.01611,8.332,5,8.5,5c.17041,0,.33319.03088.50012.04572V1A.97576.97576,0,0,0,8.922.60938,1.01847,1.01847,0,0,0,8.39075.07812.97581.97581,0,0,0,8.00012,0h-7A.97409.97409,0,0,0,.6095.07812,1.01837,1.01837,0,0,0,.07825.60938.9673.9673,0,0,0,.00012,1V9.02069A4.92958,4.92958,0,0,1,.46039,8.4613Z" fill="#fff"/>
<path d="M3.418,12.17188a.46214.46214,0,0,0,.15234.34765,1.85177,1.85177,0,0,0,.375.26953c.14844.083.31055.167.48828.25a2.73055,2.73055,0,0,1,.48829.293,1.61526,1.61526,0,0,1,.375.39844.98956.98956,0,0,1,.15234.55859,1.02408,1.02408,0,0,1-.52344.94922,1.632,1.632,0,0,1-.52734.19141,3.19774,3.19774,0,0,1-.582.05469,2.68431,2.68431,0,0,1-.27344-.01563c-.10449-.01074-.21-.02441-.31641-.043a2.33361,2.33361,0,0,1-.30859-.07422,1.08972,1.08972,0,0,1-.25-.10937V14.5a.975.975,0,0,0,.23437.168,1.66814,1.66814,0,0,0,.293.12109,2.579,2.579,0,0,0,.3086.07422,1.64373,1.64373,0,0,0,.28906.02734,2.52815,2.52815,0,0,0,.30469-.01953.89453.89453,0,0,0,.28906-.08593.62425.62425,0,0,0,.21875-.17969.48873.48873,0,0,0,.08594-.30078.47128.47128,0,0,0-.15235-.35547,1.77024,1.77024,0,0,0-.375-.26563c-.14844-.08105-.31152-.16113-.48828-.24218a2.72438,2.72438,0,0,1-.48828-.28516,1.549,1.549,0,0,1-.375-.39453,1.00486,1.00486,0,0,1-.15234-.56641.9706.9706,0,0,1,.14843-.54687,1.20875,1.20875,0,0,1,.38282-.3711,1.70905,1.70905,0,0,1,.51953-.21093A2.46975,2.46975,0,0,1,4.26953,11a5.03456,5.03456,0,0,1,.50781.02734,1.45928,1.45928,0,0,1,.48438.12891v.71094a1.25142,1.25142,0,0,0-.45313-.207,2.19048,2.19048,0,0,0-.5-.0586,1.644,1.644,0,0,0-.28906.02735,1.12291,1.12291,0,0,0-.28906.08984.6264.6264,0,0,0-.22266.17187A.42634.42634,0,0,0,3.418,12.17188Zm6.71875,1.01562a3.40218,3.40218,0,0,1-.043.53906,2.30547,2.30547,0,0,1-.40234.97656,1.91775,1.91775,0,0,1-.375.39063L10.57422,16H9.48047l-.78125-.60156c-.11524.02636-.22559.04687-.332.0625a2.30189,2.30189,0,0,1-.332.02344,2.20933,2.20933,0,0,1-.8711-.16407,1.83062,1.83062,0,0,1-.65234-.457,1.9945,1.9945,0,0,1-.40625-.69531,2.624,2.624,0,0,1-.14063-.87109,2.87385,2.87385,0,0,1,.14063-.91407,2.07515,2.07515,0,0,1,.41406-.73047,1.8639,1.8639,0,0,1,.67188-.48046A2.29457,2.29457,0,0,1,8.10547,11a2.05589,2.05589,0,0,1,.85547.17188,1.90312,1.90312,0,0,1,.64062.46484A2.009,2.009,0,0,1,10,12.332,2.65272,2.65272,0,0,1,10.13672,13.1875ZM8.03516,14.85938a1.34466,1.34466,0,0,0,.61328-.12891,1.19009,1.19009,0,0,0,.418-.34766,1.42887,1.42887,0,0,0,.23828-.51172,2.51764,2.51764,0,0,0,.07422-.61328,2.63883,2.63883,0,0,0-.07032-.60937,1.5027,1.5027,0,0,0-.22656-.51953,1.18407,1.18407,0,0,0-.40625-.35938,1.26,1.26,0,0,0-.60937-.13672,1.22053,1.22053,0,0,0-.59375.13672,1.25881,1.25881,0,0,0-.418.36328,1.57394,1.57394,0,0,0-.25.51953,2.20628,2.20628,0,0,0-.082.59766,2.237,2.237,0,0,0,.07812.59375,1.60012,1.60012,0,0,0,.23828.51563,1.15845,1.15845,0,0,0,.9961.5Zm5.30468-.05469v.60937H10.93359V11.07031h.71875v3.73438Z" fill="#fff"/>
<path d="M15.76172,11.82031a2.99728,2.99728,0,0,0-.64844-.94922,3.07314,3.07314,0,0,0-.96094-.63671A2.97165,2.97165,0,0,0,12.97656,10a4.65135,4.65135,0,0,0-.49609-1.58594A4.5455,4.5455,0,0,0,11.5,7.14453a4.49274,4.49274,0,0,0-1.36328-.83984A4.40562,4.40562,0,0,0,8.5,6a4.49586,4.49586,0,0,0-1.125.14062,4.43869,4.43869,0,0,0-1.03125.40626,4.32586,4.32586,0,0,0-.89453.65234,4.65184,4.65184,0,0,0-.72266.87109A3.8619,3.8619,0,0,0,4,8a3.925,3.925,0,0,0-1.55859.3125A3.98153,3.98153,0,0,0,.3125,10.44141a4.043,4.043,0,0,0,0,3.11718A4.003,4.003,0,0,0,1.168,14.832a3.94445,3.94445,0,0,0,.832.6167v-1.2276c-.039-.03516-.08374-.06268-.12109-.1a3.03209,3.03209,0,0,1-.64453-.95312,3.01758,3.01758,0,0,1,0-2.332A3.02292,3.02292,0,0,1,2.832,9.23438,2.91341,2.91341,0,0,1,4,9a2.88027,2.88027,0,0,1,1.23438.27344A3.49138,3.49138,0,0,1,7.44531,7.16406a3.50764,3.50764,0,0,1,2.418.10938,3.5073,3.5073,0,0,1,1.86328,1.86328A3.42177,3.42177,0,0,1,12,10.5V11h1a1.94129,1.94129,0,0,1,.77734.15625,2.01529,2.01529,0,0,1,1.06641,1.06641,2.01175,2.01175,0,0,1,0,1.55468,2.03169,2.03169,0,0,1-.42969.63672A1.999,1.999,0,0,1,14,14.72247v1.09424c.054-.01941.11108-.02838.16406-.05109A3.02292,3.02292,0,0,0,15.76562,14.168a3.03337,3.03337,0,0,0-.0039-2.34766Z" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>SQLManagedInstance</title>
<rect x="2.00012" y="2" width="5" height="1"/>
<rect x="2.00012" y="4" width="5" height="1"/>
<path d="M.46039,8.4613a4.91729,4.91729,0,0,1,.53973-.44452V1h7V5.031C8.16608,5.01611,8.332,5,8.5,5c.17041,0,.33319.03088.50012.04572V1A.97576.97576,0,0,0,8.922.60938,1.01847,1.01847,0,0,0,8.39075.07812.97581.97581,0,0,0,8.00012,0h-7A.97409.97409,0,0,0,.6095.07812,1.01837,1.01837,0,0,0,.07825.60938.9673.9673,0,0,0,.00012,1V9.02069A4.92958,4.92958,0,0,1,.46039,8.4613Z"/>
<path d="M3.418,12.17188a.46214.46214,0,0,0,.15234.34765,1.85177,1.85177,0,0,0,.375.26953c.14844.083.31055.167.48828.25a2.73055,2.73055,0,0,1,.48829.293,1.61526,1.61526,0,0,1,.375.39844.98956.98956,0,0,1,.15234.55859,1.02408,1.02408,0,0,1-.52344.94922,1.632,1.632,0,0,1-.52734.19141,3.19774,3.19774,0,0,1-.582.05469,2.68431,2.68431,0,0,1-.27344-.01563c-.10449-.01074-.21-.02441-.31641-.043a2.33361,2.33361,0,0,1-.30859-.07422,1.08972,1.08972,0,0,1-.25-.10937V14.5a.975.975,0,0,0,.23437.168,1.66814,1.66814,0,0,0,.293.12109,2.579,2.579,0,0,0,.3086.07422,1.64373,1.64373,0,0,0,.28906.02734,2.52815,2.52815,0,0,0,.30469-.01953.89453.89453,0,0,0,.28906-.08593.62425.62425,0,0,0,.21875-.17969.48873.48873,0,0,0,.08594-.30078.47128.47128,0,0,0-.15235-.35547,1.77024,1.77024,0,0,0-.375-.26563c-.14844-.08105-.31152-.16113-.48828-.24218a2.72438,2.72438,0,0,1-.48828-.28516,1.549,1.549,0,0,1-.375-.39453,1.00486,1.00486,0,0,1-.15234-.56641.9706.9706,0,0,1,.14843-.54687,1.20875,1.20875,0,0,1,.38282-.3711,1.70905,1.70905,0,0,1,.51953-.21093A2.46975,2.46975,0,0,1,4.26953,11a5.03456,5.03456,0,0,1,.50781.02734,1.45928,1.45928,0,0,1,.48438.12891v.71094a1.25142,1.25142,0,0,0-.45313-.207,2.19048,2.19048,0,0,0-.5-.0586,1.644,1.644,0,0,0-.28906.02735,1.12291,1.12291,0,0,0-.28906.08984.6264.6264,0,0,0-.22266.17187A.42634.42634,0,0,0,3.418,12.17188Zm6.71875,1.01562a3.40218,3.40218,0,0,1-.043.53906,2.30547,2.30547,0,0,1-.40234.97656,1.91775,1.91775,0,0,1-.375.39063L10.57422,16H9.48047l-.78125-.60156c-.11524.02636-.22559.04687-.332.0625a2.30189,2.30189,0,0,1-.332.02344,2.20933,2.20933,0,0,1-.8711-.16407,1.83062,1.83062,0,0,1-.65234-.457,1.9945,1.9945,0,0,1-.40625-.69531,2.624,2.624,0,0,1-.14063-.87109,2.87385,2.87385,0,0,1,.14063-.91407,2.07515,2.07515,0,0,1,.41406-.73047,1.8639,1.8639,0,0,1,.67188-.48046A2.29457,2.29457,0,0,1,8.10547,11a2.05589,2.05589,0,0,1,.85547.17188,1.90312,1.90312,0,0,1,.64062.46484A2.009,2.009,0,0,1,10,12.332,2.65272,2.65272,0,0,1,10.13672,13.1875ZM8.03516,14.85938a1.34466,1.34466,0,0,0,.61328-.12891,1.19009,1.19009,0,0,0,.418-.34766,1.42887,1.42887,0,0,0,.23828-.51172,2.51764,2.51764,0,0,0,.07422-.61328,2.63883,2.63883,0,0,0-.07032-.60937,1.5027,1.5027,0,0,0-.22656-.51953,1.18407,1.18407,0,0,0-.40625-.35938,1.26,1.26,0,0,0-.60937-.13672,1.22053,1.22053,0,0,0-.59375.13672,1.25881,1.25881,0,0,0-.418.36328,1.57394,1.57394,0,0,0-.25.51953,2.20628,2.20628,0,0,0-.082.59766,2.237,2.237,0,0,0,.07812.59375,1.60012,1.60012,0,0,0,.23828.51563,1.15845,1.15845,0,0,0,.9961.5Zm5.30468-.05469v.60937H10.93359V11.07031h.71875v3.73438Z"/>
<path d="M15.76172,11.82031a2.99728,2.99728,0,0,0-.64844-.94922,3.07314,3.07314,0,0,0-.96094-.63671A2.97165,2.97165,0,0,0,12.97656,10a4.65135,4.65135,0,0,0-.49609-1.58594A4.5455,4.5455,0,0,0,11.5,7.14453a4.49274,4.49274,0,0,0-1.36328-.83984A4.40562,4.40562,0,0,0,8.5,6a4.49586,4.49586,0,0,0-1.125.14062,4.43869,4.43869,0,0,0-1.03125.40626,4.32586,4.32586,0,0,0-.89453.65234,4.65184,4.65184,0,0,0-.72266.87109A3.8619,3.8619,0,0,0,4,8a3.925,3.925,0,0,0-1.55859.3125A3.98153,3.98153,0,0,0,.3125,10.44141a4.043,4.043,0,0,0,0,3.11718A4.003,4.003,0,0,0,1.168,14.832a3.94445,3.94445,0,0,0,.832.6167v-1.2276c-.039-.03516-.08374-.06268-.12109-.1a3.03209,3.03209,0,0,1-.64453-.95312,3.01758,3.01758,0,0,1,0-2.332A3.02292,3.02292,0,0,1,2.832,9.23438,2.91341,2.91341,0,0,1,4,9a2.88027,2.88027,0,0,1,1.23438.27344A3.49138,3.49138,0,0,1,7.44531,7.16406a3.50764,3.50764,0,0,1,2.418.10938,3.5073,3.5073,0,0,1,1.86328,1.86328A3.42177,3.42177,0,0,1,12,10.5V11h1a1.94129,1.94129,0,0,1,.77734.15625,2.01529,2.01529,0,0,1,1.06641,1.06641,2.01175,2.01175,0,0,1,0,1.55468,2.03169,2.03169,0,0,1-.42969.63672A1.999,1.999,0,0,1,14,14.72247v1.09424c.054-.01941.11108-.02838.16406-.05109A3.02292,3.02292,0,0,0,15.76562,14.168a3.03337,3.03337,0,0,0-.0039-2.34766Z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

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)];

View File

@@ -27,6 +27,8 @@ import { AzureResourceCacheService } from './azureResource/services/cacheService
import { AzureResourceTenantService } from './azureResource/services/tenantService';
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';
let extensionContext: vscode.ExtensionContext;
@@ -74,7 +76,8 @@ export async function activate(context: vscode.ExtensionContext) {
provideResources() {
return [
new AzureResourceDatabaseServerProvider(new AzureResourceDatabaseServerService(), apiWrapper, extensionContext),
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), apiWrapper, extensionContext)
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), apiWrapper, extensionContext),
new SqlInstanceProvider(new SqlInstanceResourceService(), apiWrapper, extensionContext)
];
}
};