mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Feature/extensible azure resource explorer (#3504)
Extensible Azure Resource Explorer
This commit is contained in:
@@ -5,21 +5,30 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { ExtensionContext } from "vscode";
|
||||
import { ExtensionContext } from 'vscode';
|
||||
|
||||
import { IAzureResourceCacheService } from "../interfaces";
|
||||
import { IAzureResourceCacheService } from '../interfaces';
|
||||
|
||||
export class AzureResourceCacheService implements IAzureResourceCacheService {
|
||||
public constructor(
|
||||
public readonly context: ExtensionContext
|
||||
context: ExtensionContext
|
||||
) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
public get<T>(key: string): T | undefined {
|
||||
return this.context.workspaceState.get(key);
|
||||
public generateKey(id: string): string {
|
||||
return `${AzureResourceCacheService.cacheKeyPrefix}.${id}`;
|
||||
}
|
||||
|
||||
public get<T>(key: string): T | undefined {
|
||||
return this._context.workspaceState.get(key);
|
||||
}
|
||||
|
||||
public update<T>(key: string, value: T): void {
|
||||
this.context.workspaceState.update(key, value);
|
||||
this._context.workspaceState.update(key, value);
|
||||
}
|
||||
|
||||
private _context: ExtensionContext = undefined;
|
||||
|
||||
private static readonly cacheKeyPrefix = 'azure.resource.cache';
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { ExtensionContext } from "vscode";
|
||||
import { ApiWrapper } from "../../apiWrapper";
|
||||
|
||||
import { IAzureResourceContextService } from "../interfaces";
|
||||
|
||||
export class AzureResourceContextService implements IAzureResourceContextService {
|
||||
public constructor(
|
||||
context: ExtensionContext,
|
||||
apiWrapper: ApiWrapper
|
||||
) {
|
||||
this._context = context;
|
||||
this._apiWrapper = apiWrapper;
|
||||
}
|
||||
|
||||
public getAbsolutePath(relativePath: string): string {
|
||||
return this._context.asAbsolutePath(relativePath);
|
||||
}
|
||||
|
||||
public executeCommand(commandId: string, ...args: any[]): void {
|
||||
this._apiWrapper.executeCommand(commandId, args);
|
||||
}
|
||||
|
||||
public showErrorMessage(errorMessage: string): void {
|
||||
this._apiWrapper.showErrorMessage(errorMessage);
|
||||
}
|
||||
|
||||
private _context: ExtensionContext = undefined;
|
||||
private _apiWrapper: ApiWrapper = undefined;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import { TokenCredentials, ServiceClientCredentials } from 'ms-rest';
|
||||
import { ApiWrapper } from '../../apiWrapper';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { IAzureResourceCredentialService } from '../interfaces';
|
||||
import { AzureResourceCredentialError } from '../errors';
|
||||
|
||||
export class AzureResourceCredentialService implements IAzureResourceCredentialService {
|
||||
public constructor(
|
||||
apiWrapper: ApiWrapper
|
||||
) {
|
||||
this._apiWrapper = apiWrapper;
|
||||
}
|
||||
|
||||
public async getCredentials(account: sqlops.Account, resource: sqlops.AzureResource): Promise<ServiceClientCredentials[]> {
|
||||
try {
|
||||
let credentials: TokenCredentials[] = [];
|
||||
let tokens = await this._apiWrapper.getSecurityToken(account, resource);
|
||||
|
||||
for (let tenant of account.properties.tenants) {
|
||||
let token = tokens[tenant.id].token;
|
||||
let tokenType = tokens[tenant.id].tokenType;
|
||||
|
||||
credentials.push(new TokenCredentials(token, tokenType));
|
||||
}
|
||||
|
||||
return credentials;
|
||||
} catch (error) {
|
||||
throw new AzureResourceCredentialError(localize('azureResource.services.credentialService.credentialError', 'Failed to get credential for account {0}. Please refresh the account.', account.key.accountId), error);
|
||||
}
|
||||
}
|
||||
|
||||
private _apiWrapper: ApiWrapper = undefined;
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { SqlManagementClient } from 'azure-arm-sql';
|
||||
|
||||
import { IAzureResourceDatabaseServerService } from '../interfaces';
|
||||
import { AzureResourceSubscription, AzureResourceDatabaseServer } from '../models';
|
||||
|
||||
export class AzureResourceDatabaseServerService implements IAzureResourceDatabaseServerService {
|
||||
public async getDatabaseServers(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabaseServer[]> {
|
||||
let databaseServers: AzureResourceDatabaseServer[] = [];
|
||||
for (let cred of credentials) {
|
||||
let sqlManagementClient = new SqlManagementClient(cred, subscription.id);
|
||||
try {
|
||||
let svrs = await sqlManagementClient.servers.list();
|
||||
svrs.forEach((svr) => databaseServers.push({
|
||||
name: svr.name,
|
||||
fullName: svr.fullyQualifiedDomainName,
|
||||
loginName: svr.administratorLogin,
|
||||
defaultDatabaseName: 'master'
|
||||
}));
|
||||
} catch (error) {
|
||||
if (error.code === 'InvalidAuthenticationTokenTenant' && error.statusCode === 401) {
|
||||
/**
|
||||
* There may be multiple tenants for an account and it may throw exceptions like following. Just swallow the exception here.
|
||||
* The access token is from the wrong issuer. It must match one of the tenants associated with this subscription.
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return databaseServers;
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { SqlManagementClient } from 'azure-arm-sql';
|
||||
|
||||
import { IAzureResourceDatabaseService } from '../interfaces';
|
||||
import { AzureResourceSubscription, AzureResourceDatabase } from '../models';
|
||||
|
||||
export class AzureResourceDatabaseService implements IAzureResourceDatabaseService {
|
||||
public async getDatabases(subscription: AzureResourceSubscription, credentials: ServiceClientCredentials[]): Promise<AzureResourceDatabase[]> {
|
||||
let databases: AzureResourceDatabase[] = [];
|
||||
for (let cred of credentials) {
|
||||
let sqlManagementClient = new SqlManagementClient(cred, subscription.id);
|
||||
try {
|
||||
let svrs = await sqlManagementClient.servers.list();
|
||||
for (let svr of svrs) {
|
||||
// Extract resource group name from svr.id
|
||||
let svrIdRegExp = new RegExp(`\/subscriptions\/${subscription.id}\/resourceGroups\/(.+)\/providers\/Microsoft\.Sql\/servers\/${svr.name}`);
|
||||
if (!svrIdRegExp.test(svr.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let founds = svrIdRegExp.exec(svr.id);
|
||||
let resouceGroup = founds[1];
|
||||
|
||||
let 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
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.code === 'InvalidAuthenticationTokenTenant' && error.statusCode === 401) {
|
||||
/**
|
||||
* There may be multiple tenants for an account and it may throw exceptions like following. Just swallow the exception here.
|
||||
* The access token is from the wrong issuer. It must match one of the tenants associated with this subscription.
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return databases;
|
||||
}
|
||||
}
|
||||
@@ -8,11 +8,11 @@
|
||||
import { WorkspaceConfiguration, ConfigurationTarget } from 'vscode';
|
||||
import { Account } from 'sqlops';
|
||||
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { IAzureResourceSubscriptionFilterService, IAzureResourceCacheService } from '../interfaces';
|
||||
import { AzureResourceSubscription } from '../models';
|
||||
|
||||
interface AzureResourceSelectedSubscriptionsCache {
|
||||
selectedSubscriptions: { [accountId: string]: AzureResourceSubscription[]};
|
||||
selectedSubscriptions: { [accountId: string]: azureResource.AzureResourceSubscription[]};
|
||||
}
|
||||
|
||||
export class AzureResourceSubscriptionFilterService implements IAzureResourceSubscriptionFilterService {
|
||||
@@ -20,12 +20,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
||||
cacheService: IAzureResourceCacheService
|
||||
) {
|
||||
this._cacheService = cacheService;
|
||||
|
||||
this._cacheKey = this._cacheService.generateKey('selectedSubscriptions');
|
||||
}
|
||||
|
||||
public async getSelectedSubscriptions(account: Account): Promise<AzureResourceSubscription[]> {
|
||||
let selectedSubscriptions: AzureResourceSubscription[] = [];
|
||||
public async getSelectedSubscriptions(account: Account): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
let selectedSubscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||
if (cache) {
|
||||
selectedSubscriptions = cache.selectedSubscriptions[account.key.accountId];
|
||||
}
|
||||
@@ -33,10 +35,10 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
||||
return selectedSubscriptions;
|
||||
}
|
||||
|
||||
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: AzureResourceSubscription[]): Promise<void> {
|
||||
let selectedSubscriptionsCache: { [accountId: string]: AzureResourceSubscription[]} = {};
|
||||
public async saveSelectedSubscriptions(account: Account, selectedSubscriptions: azureResource.AzureResourceSubscription[]): Promise<void> {
|
||||
let selectedSubscriptionsCache: { [accountId: string]: azureResource.AzureResourceSubscription[]} = {};
|
||||
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey);
|
||||
const cache = this._cacheService.get<AzureResourceSelectedSubscriptionsCache>(this._cacheKey);
|
||||
if (cache) {
|
||||
selectedSubscriptionsCache = cache.selectedSubscriptions;
|
||||
}
|
||||
@@ -47,14 +49,14 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
||||
|
||||
selectedSubscriptionsCache[account.key.accountId] = selectedSubscriptions;
|
||||
|
||||
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(AzureResourceSubscriptionFilterService.CacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
||||
this._cacheService.update<AzureResourceSelectedSubscriptionsCache>(this._cacheKey, { selectedSubscriptions: selectedSubscriptionsCache });
|
||||
|
||||
const filters: string[] = [];
|
||||
for (const accountId in selectedSubscriptionsCache) {
|
||||
filters.push(...selectedSubscriptionsCache[accountId].map((subcription) => `${accountId}/${subcription.id}/${subcription.name}`));
|
||||
}
|
||||
|
||||
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.FilterConfigName);
|
||||
const resourceFilterConfig = this._config.inspect<string[]>(AzureResourceSubscriptionFilterService.filterConfigName);
|
||||
let configTarget = ConfigurationTarget.Global;
|
||||
if (resourceFilterConfig) {
|
||||
if (resourceFilterConfig.workspaceFolderValue) {
|
||||
@@ -66,12 +68,12 @@ export class AzureResourceSubscriptionFilterService implements IAzureResourceSub
|
||||
}
|
||||
}
|
||||
|
||||
await this._config.update(AzureResourceSubscriptionFilterService.FilterConfigName, filters, configTarget);
|
||||
await this._config.update(AzureResourceSubscriptionFilterService.filterConfigName, filters, configTarget);
|
||||
}
|
||||
|
||||
private _config: WorkspaceConfiguration = undefined;
|
||||
private _cacheService: IAzureResourceCacheService = undefined;
|
||||
private _cacheKey: string = undefined;
|
||||
|
||||
private static readonly FilterConfigName = 'resourceFilter';
|
||||
private static readonly CacheKey = 'azureResource.cache.selectedSubscriptions';
|
||||
private static readonly filterConfigName = 'azure.resource.config.filter';
|
||||
}
|
||||
|
||||
@@ -9,24 +9,19 @@ import { Account } from 'sqlops';
|
||||
import { ServiceClientCredentials } from 'ms-rest';
|
||||
import { SubscriptionClient } from 'azure-arm-resource';
|
||||
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { IAzureResourceSubscriptionService } from '../interfaces';
|
||||
import { AzureResourceSubscription } from '../models';
|
||||
|
||||
export class AzureResourceSubscriptionService implements IAzureResourceSubscriptionService {
|
||||
public async getSubscriptions(account: Account, credentials: ServiceClientCredentials[]): Promise<AzureResourceSubscription[]> {
|
||||
let subscriptions: AzureResourceSubscription[] = [];
|
||||
for (let cred of credentials) {
|
||||
let subClient = new SubscriptionClient.SubscriptionClient(cred);
|
||||
try {
|
||||
let subs = await subClient.subscriptions.list();
|
||||
subs.forEach((sub) => subscriptions.push({
|
||||
id: sub.subscriptionId,
|
||||
name: sub.displayName
|
||||
}));
|
||||
} catch (error) {
|
||||
// Swallow the exception here.
|
||||
}
|
||||
}
|
||||
public async getSubscriptions(account: Account, credential: ServiceClientCredentials): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
const subscriptions: azureResource.AzureResourceSubscription[] = [];
|
||||
|
||||
const subClient = new SubscriptionClient.SubscriptionClient(credential);
|
||||
const subs = await subClient.subscriptions.list();
|
||||
subs.forEach((sub) => subscriptions.push({
|
||||
id: sub.subscriptionId,
|
||||
name: sub.displayName
|
||||
}));
|
||||
|
||||
return subscriptions;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as request from 'request';
|
||||
|
||||
import { azureResource } from '../azure-resource';
|
||||
import { IAzureResourceTenantService } from '../interfaces';
|
||||
|
||||
export class AzureResourceTenantService implements IAzureResourceTenantService {
|
||||
public async getTenantId(subscription: azureResource.AzureResourceSubscription): Promise<string> {
|
||||
const requestPromisified = new Promise<string>((resolve, reject) => {
|
||||
const url = `https://management.azure.com/subscriptions/${subscription.id}?api-version=2014-04-01`;
|
||||
request(url, function (error, response, body) {
|
||||
if (response.statusCode === 401) {
|
||||
const tenantIdRegEx = /authorization_uri="https:\/\/login\.windows\.net\/([0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})"/;
|
||||
const teantIdString = response.headers['www-authenticate'];
|
||||
if (tenantIdRegEx.test(teantIdString)) {
|
||||
resolve(tenantIdRegEx.exec(teantIdString)[1]);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return await requestPromisified;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user