diff --git a/extensions/azurecore/resources/dark/sql_instance_inverse.svg b/extensions/azurecore/resources/dark/sql_instance_inverse.svg new file mode 100644 index 0000000000..0ba62f0cf1 --- /dev/null +++ b/extensions/azurecore/resources/dark/sql_instance_inverse.svg @@ -0,0 +1,8 @@ + + SQLManagedInstance_white + + + + + + diff --git a/extensions/azurecore/resources/light/sql_instance.svg b/extensions/azurecore/resources/light/sql_instance.svg new file mode 100644 index 0000000000..e6c8ee21da --- /dev/null +++ b/extensions/azurecore/resources/light/sql_instance.svg @@ -0,0 +1,8 @@ + + SQLManagedInstance + + + + + + diff --git a/extensions/azurecore/src/azureResource/commands.ts b/extensions/azurecore/src/azureResource/commands.ts index 2782941e6d..00a156e8a6 100644 --- a/extensions/azurecore/src/azureResource/commands.ts +++ b/extensions/azurecore/src/azureResource/commands.ts @@ -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) { diff --git a/extensions/azurecore/src/azureResource/providers/resourceTreeDataProviderBase.ts b/extensions/azurecore/src/azureResource/providers/resourceTreeDataProviderBase.ts index 3ab46b1d77..f14c95975b 100644 --- a/extensions/azurecore/src/azureResource/providers/resourceTreeDataProviderBase.ts +++ b/extensions/azurecore/src/azureResource/providers/resourceTreeDataProviderBase.ts @@ -30,14 +30,14 @@ export abstract class ResourceTreeDataProviderBase 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)) || []; + const resources: T[] = await this._resourceService.getResources(element.subscription, credential) || []; return resources.map((resource) => { 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; diff --git a/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceProvider.ts b/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceProvider.ts new file mode 100644 index 0000000000..ec3ea673fe --- /dev/null +++ b/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceProvider.ts @@ -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, + 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'; + } +} diff --git a/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceService.ts b/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceService.ts new file mode 100644 index 0000000000..83aa8f22b7 --- /dev/null +++ b/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceService.ts @@ -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 { + public async getResources(subscription: azureResource.AzureResourceSubscription, credential: OldSc): Promise { + 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; + } +} diff --git a/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceTreeDataProvider.ts new file mode 100644 index 0000000000..14516a683f --- /dev/null +++ b/extensions/azurecore/src/azureResource/providers/sqlinstance/sqlInstanceTreeDataProvider.ts @@ -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 { + private static readonly containerId = 'azure.resource.providers.sqlInstanceContainer'; + private static readonly containerLabel = localize('azure.resource.providers.sqlInstanceContainerLabel', "SQL Instances"); + + public constructor( + databaseServerService: IAzureResourceService, + 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 + } + }; + } +} diff --git a/extensions/azurecore/src/azureResource/tree/accountTreeNode.ts b/extensions/azurecore/src/azureResource/tree/accountTreeNode.ts index 6e140cfad6..cf84497772 100644 --- a/extensions/azurecore/src/azureResource/tree/accountTreeNode.ts +++ b/extensions/azurecore/src/azureResource/tree/accountTreeNode.ts @@ -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."); -} \ No newline at end of file +} diff --git a/extensions/azurecore/src/azureResource/tree/subscriptionTreeNode.ts b/extensions/azurecore/src/azureResource/tree/subscriptionTreeNode.ts index c166a81bdf..02130cbff5 100644 --- a/extensions/azurecore/src/azureResource/tree/subscriptionTreeNode.ts +++ b/extensions/azurecore/src/azureResource/tree/subscriptionTreeNode.ts @@ -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)]; diff --git a/extensions/azurecore/src/extension.ts b/extensions/azurecore/src/extension.ts index 5b1fd10bc0..17bd5af621 100644 --- a/extensions/azurecore/src/extension.ts +++ b/extensions/azurecore/src/extension.ts @@ -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) ]; } };