Open in azure portal (#10535)

* initial commit to get it working

* Change configuration to use package.nls.json

* Make the necessary plumbing

* Support multi cloud

* Move the menu item to the bottom

* Fix failing tests

* Fix the tests
This commit is contained in:
Amir Omidi
2020-05-29 19:59:29 -07:00
committed by GitHub
parent b2e0291a95
commit 7d31239e64
28 changed files with 146 additions and 31 deletions

View File

@@ -151,6 +151,10 @@
"light": "resources/light/console.svg"
}
},
{
"command": "azure.resource.openInAzurePortal",
"title": "%azure.openInAzurePortal.title%"
},
{
"command": "azure.resource.connectsqlserver",
"title": "%azure.resource.connectsqlserver.title%",

View File

@@ -18,6 +18,8 @@
"azure.accounts.getSubscriptions.title": "Get Azure Account Subscriptions",
"azure.accounts.getResourceGroups.title": "Get Azure Account Subscription Resource Groups",
"azure.openInAzurePortal.title": "Open in Azure Portal",
"config.azureAccountConfigurationSection": "Azure Account Configuration",
"config.enablePublicCloudDescription": "Should Azure public cloud integration be enabled",
"config.enableUsGovCloudDescription": "Should US Government Azure cloud (Fairfax) integration be enabled",

View File

@@ -118,6 +118,8 @@ interface Settings {
redirectUri?: string;
scopes?: string[]
portalEndpoint?: string
}
/**

View File

@@ -52,7 +52,8 @@ const publicAzureSettings: ProviderSettings = {
scopes: [
'openid', 'email', 'profile', 'offline_access',
'https://management.azure.com/user_impersonation',
]
],
portalEndpoint: 'https://portal.azure.com'
}
}
};
@@ -101,7 +102,8 @@ const usGovAzureSettings: ProviderSettings = {
scopes: [
'openid', 'email', 'profile', 'offline_access',
'https://management.usgovcloudapi.net/user_impersonation'
]
],
portalEndpoint: 'https://portal.azure.us'
}
}
};

View File

@@ -24,6 +24,7 @@ export namespace azureResource {
export interface AzureResource {
name: string;
id: string;
tenant?: string;
}
export interface AzureResourceSubscription extends AzureResource {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { window, QuickPickItem } from 'vscode';
import { window, QuickPickItem, env, Uri } from 'vscode';
import * as azdata from 'azdata';
import { TokenCredentials } from '@azure/ms-rest-js';
import * as nls from 'vscode-nls';
@@ -237,4 +237,23 @@ export function registerAzureResourceCommands(appContext: AppContext, tree: Azur
appContext.apiWrapper.executeCommand('workbench.view.connections');
}
});
appContext.apiWrapper.registerCommand('azure.resource.openInAzurePortal', async (node?: TreeNode) => {
if (!node) {
return;
}
const treeItem: azdata.TreeItem = await node.getTreeItem();
if (!treeItem.payload) {
return;
}
let connectionProfile = Object.assign({}, treeItem.payload, { saveProfile: true });
if (!connectionProfile.azureResourceId) {
return;
}
const urlToOpen = `${connectionProfile.azurePortalEndpoint}//${connectionProfile.azureTenantId}/#resource/${connectionProfile.azureResourceId}`;
env.openExternal(Uri.parse(urlToOpen));
});
}

View File

@@ -50,7 +50,8 @@ export class AzureResourceDatabaseService implements IAzureResourceService<azure
id: db.id,
serverName: server.name,
serverFullName: server.properties.fullyQualifiedDomainName,
loginName: server.properties.administratorLogin
loginName: server.properties.administratorLogin,
tenant: db.tenantId
});
}
}

View File

@@ -51,7 +51,10 @@ export class AzureResourceDatabaseTreeDataProvider extends ResourceTreeDataProvi
providerName: 'MSSQL',
saveProfile: false,
options: {},
azureAccount: account.key.accountId
azureAccount: account.key.accountId,
azureResourceId: database.id,
azureTenantId: database.tenant,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: 'MSSQL',
type: ExtensionNodeType.Database

View File

@@ -29,7 +29,8 @@ export class AzureResourceDatabaseServerService extends ResourceServiceBase<DbSe
name: resource.name,
fullName: resource.properties.fullyQualifiedDomainName,
loginName: resource.properties.administratorLogin,
defaultDatabaseName: 'master'
defaultDatabaseName: 'master',
tenant: resource.tenantId
};
}
}

View File

@@ -52,7 +52,10 @@ export class AzureResourceDatabaseServerTreeDataProvider extends ResourceTreeDat
providerName: 'MSSQL',
saveProfile: false,
options: {},
azureAccount: account.key.accountId
azureAccount: account.key.accountId,
azureTenantId: databaseServer.tenant,
azureResourceId: databaseServer.id,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: 'MSSQL',
type: ExtensionNodeType.Server

View File

@@ -26,7 +26,8 @@ export class PostgresServerArcService extends ResourceServiceBase<PostgresArcSer
name: resource.name,
fullName: resource.name,
loginName: resource.properties.admin,
defaultDatabaseName: 'postgres'
defaultDatabaseName: 'postgres',
tenant: resource.tenantId
};
}
}

View File

@@ -55,7 +55,10 @@ export class PostgresServerArcTreeDataProvider extends ResourceTreeDataProviderB
// Set default for SSL or will get error complaining about it not being set correctly
'sslmode': 'require'
},
azureAccount: account.key.accountId
azureAccount: account.key.accountId,
azureTenantId: databaseServer.tenant,
azureResourceId: databaseServer.id,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: 'PGSQL',
type: ExtensionNodeType.Server

View File

@@ -29,7 +29,8 @@ export class PostgresServerService extends ResourceServiceBase<DbServerGraphData
name: resource.name,
fullName: resource.properties.fullyQualifiedDomainName,
loginName: resource.properties.administratorLogin,
defaultDatabaseName: 'postgres'
defaultDatabaseName: 'postgres',
tenant: resource.tenantId
};
}
}

View File

@@ -55,7 +55,10 @@ export class PostgresServerTreeDataProvider extends ResourceTreeDataProviderBase
// Set default for SSL or will get error complaining about it not being set correctly
'sslmode': 'require'
},
azureAccount: account.key.accountId
azureAccount: account.key.accountId,
azureTenantId: databaseServer.tenant,
azureResourceId: databaseServer.id,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: 'PGSQL',
type: ExtensionNodeType.Server

View File

@@ -58,6 +58,7 @@ export abstract class ResourceTreeDataProviderBase<T extends azureResource.Azure
}
export interface GraphData {
tenantId: string;
id: string;
name: string;
location: string;

View File

@@ -27,7 +27,8 @@ export class SqlInstanceResourceService extends ResourceServiceBase<SqlInstanceG
name: resource.name,
fullName: resource.properties.fullyQualifiedDomainName,
loginName: resource.properties.administratorLogin,
defaultDatabaseName: 'master'
defaultDatabaseName: 'master',
tenant: resource.tenantId
};
}
}

View File

@@ -52,7 +52,10 @@ export class SqlInstanceTreeDataProvider extends ResourceTreeDataProviderBase<az
providerName: 'MSSQL',
saveProfile: false,
options: {},
azureAccount: account.key.accountId
azureAccount: account.key.accountId,
azureTenantId: databaseServer.tenant,
azureResourceId: databaseServer.id,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: 'MSSQL',
type: ExtensionNodeType.Server

View File

@@ -26,7 +26,8 @@ export class SqlInstanceArcResourceService extends ResourceServiceBase<SqlInstan
name: resource.name,
fullName: resource.name,
loginName: resource.properties.admin,
defaultDatabaseName: 'master'
defaultDatabaseName: 'master',
tenant: resource.tenantId
};
}
}

View File

@@ -52,7 +52,10 @@ export class SqlInstanceArcTreeDataProvider extends ResourceTreeDataProviderBase
providerName: 'MSSQL',
saveProfile: false,
options: {},
azureAccount: account.key.accountId
azureAccount: account.key.accountId,
azureTenantId: databaseServer.tenant,
azureResourceId: databaseServer.id,
azurePortalEndpoint: account.properties.providerSettings.settings.portalEndpoint
},
childProvider: 'MSSQL',
type: ExtensionNodeType.Server

View File

@@ -14,6 +14,8 @@ import { ApiWrapper } from '../../../../apiWrapper';
import { AzureResourceDatabaseTreeDataProvider } from '../../../../azureResource/providers/database/databaseTreeDataProvider';
import { AzureResourceItemType } from '../../../../azureResource/constants';
import { IAzureResourceService } from '../../../../azureResource/interfaces';
import { AzureAccount } from '../../../../account-provider/interfaces';
import settings from '../../../../account-provider/providerSettings';
// Mock services
let mockDatabaseService: TypeMoq.IMock<IAzureResourceService<azureResource.AzureResourceDatabase>>;
@@ -21,7 +23,7 @@ let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
// Mock test data
const mockAccount: azdata.Account = {
const mockAccount: AzureAccount = {
key: {
accountId: 'mock_account',
providerId: 'mock_provider'
@@ -32,7 +34,11 @@ const mockAccount: azdata.Account = {
contextualDisplayName: 'test',
userId: 'test@email.com'
},
properties: undefined,
properties: {
providerSettings: settings[0].metadata,
isMsAccount: true,
tenants: []
},
isStale: false
};

View File

@@ -19,9 +19,11 @@ import { IAzureResourceService } from '../../../../azureResource/interfaces';
let mockDatabaseServerService: TypeMoq.IMock<IAzureResourceService<azureResource.AzureResourceDatabaseServer>>;
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
import settings from '../../../../account-provider/providerSettings';
import { AzureAccount } from '../../../../account-provider/interfaces';
// Mock test data
const mockAccount: azdata.Account = {
const mockAccount: AzureAccount = {
key: {
accountId: 'mock_account',
providerId: 'mock_provider'
@@ -32,7 +34,11 @@ const mockAccount: azdata.Account = {
contextualDisplayName: 'test',
userId: 'test@email.com'
},
properties: undefined,
properties: {
providerSettings: settings[0].metadata,
isMsAccount: true,
tenants: []
},
isStale: false
};

View File

@@ -5,15 +5,16 @@
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as azdata from 'azdata';
import 'mocha';
import { fail } from 'assert';
import { azureResource } from '../../azureResource/azure-resource';
import { AzureResourceService } from '../../azureResource/resourceService';
import { AzureAccount } from '../../account-provider/interfaces';
import settings from '../../account-provider/providerSettings';
// Mock test data
const mockAccount: azdata.Account = {
const mockAccount: AzureAccount = {
key: {
accountId: 'mock_account',
providerId: 'mock_provider'
@@ -24,7 +25,11 @@ const mockAccount: azdata.Account = {
contextualDisplayName: 'test',
userId: 'test@email.com'
},
properties: undefined,
properties: {
providerSettings: settings[0].metadata,
isMsAccount: true,
tenants: []
},
isStale: false
};

View File

@@ -5,7 +5,6 @@
import * as should from 'should';
import * as TypeMoq from 'typemoq';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
import 'mocha';
@@ -15,9 +14,11 @@ import { AzureResourceResourceTreeNode } from '../../azureResource/resourceTreeN
import { AppContext } from '../../appContext';
import { ApiWrapper } from '../../apiWrapper';
import { AzureResourceServiceNames } from '../../azureResource/constants';
import settings from '../../account-provider/providerSettings';
import { AzureAccount } from '../../account-provider/interfaces';
// Mock test data
const mockAccount: azdata.Account = {
const mockAccount: AzureAccount = {
key: {
accountId: 'mock_account',
providerId: 'mock_provider'
@@ -28,7 +29,11 @@ const mockAccount: azdata.Account = {
contextualDisplayName: 'test',
userId: 'test@email.com'
},
properties: undefined,
properties: {
providerSettings: settings[0].metadata,
isMsAccount: true,
tenants: []
},
isStale: false
};
@@ -87,7 +92,7 @@ let mockResourceProvider: TypeMoq.IMock<azureResource.IAzureResourceProvider>;
let resourceService: AzureResourceService;
let appContext: AppContext;
describe('AzureResourceResourceTreeNode.info', function(): void {
describe('AzureResourceResourceTreeNode.info', function (): void {
beforeEach(() => {
mockResourceTreeDataProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider.setup((o) => o.getTreeItem(mockResourceRootNode)).returns(() => mockResourceRootNode.treeItem);
@@ -105,7 +110,7 @@ describe('AzureResourceResourceTreeNode.info', function(): void {
appContext.registerService(AzureResourceServiceNames.resourceService, resourceService);
});
it('Should be correct when created.', async function(): Promise<void> {
it('Should be correct when created.', async function (): Promise<void> {
const resourceTreeNode = new AzureResourceResourceTreeNode({
resourceProviderId: mockResourceProviderId,
resourceNode: mockResourceRootNode
@@ -127,7 +132,7 @@ describe('AzureResourceResourceTreeNode.info', function(): void {
});
});
describe('AzureResourceResourceTreeNode.getChildren', function(): void {
describe('AzureResourceResourceTreeNode.getChildren', function (): void {
beforeEach(() => {
mockResourceTreeDataProvider = TypeMoq.Mock.ofType<azureResource.IAzureResourceTreeDataProvider>();
mockResourceTreeDataProvider.setup((o) => o.getChildren(mockResourceRootNode)).returns(() => Promise.resolve(mockResourceNodes));
@@ -145,12 +150,12 @@ describe('AzureResourceResourceTreeNode.getChildren', function(): void {
appContext.registerService(AzureResourceServiceNames.resourceService, resourceService);
});
it('Should return resource nodes when it is container node.', async function(): Promise<void> {
it('Should return resource nodes when it is container node.', async function (): Promise<void> {
const resourceTreeNode = new AzureResourceResourceTreeNode({
resourceProviderId: mockResourceProviderId,
resourceNode: mockResourceRootNode
},
undefined, appContext);
undefined, appContext);
const children = await resourceTreeNode.getChildren();
@@ -176,7 +181,7 @@ describe('AzureResourceResourceTreeNode.getChildren', function(): void {
}
});
it('Should return empty when it is leaf node.', async function(): Promise<void> {
it('Should return empty when it is leaf node.', async function (): Promise<void> {
const resourceTreeNode = new AzureResourceResourceTreeNode({
resourceProviderId: mockResourceProviderId,
resourceNode: mockResourceNode1