From 10f5b8b76ebf7b8fd0f0c70e62c040693d831273 Mon Sep 17 00:00:00 2001 From: Raymond Truong Date: Mon, 15 Aug 2022 14:37:41 -0700 Subject: [PATCH] [Azure Core, SQL Migration] Fix wrong endpoints being used for non-public clouds (#20304) * Add correct host to ARM REST API calls * Clean up * Missed a spot * One more comment * Expose new function getProviderMetadataForAccount in azurecore API --- .../azurecore/src/azureResource/utils.ts | 29 ++++++-- extensions/azurecore/src/azurecore.d.ts | 1 + extensions/azurecore/src/extension.ts | 3 + extensions/machine-learning/src/test/stubs.ts | 3 + extensions/sql-migration/src/api/azure.ts | 66 ++++++++++++------- 5 files changed, 70 insertions(+), 32 deletions(-) diff --git a/extensions/azurecore/src/azureResource/utils.ts b/extensions/azurecore/src/azureResource/utils.ts index 7021bfde0b..330a2e06cc 100644 --- a/extensions/azurecore/src/azureResource/utils.ts +++ b/extensions/azurecore/src/azureResource/utils.ts @@ -7,7 +7,7 @@ import { ResourceGraphClient } from '@azure/arm-resourcegraph'; import { TokenCredentials } from '@azure/ms-rest-js'; import axios, { AxiosRequestConfig } from 'axios'; import * as azdata from 'azdata'; -import { AzureRestResponse, GetResourceGroupsResult, GetSubscriptionsResult, ResourceQueryResult, GetBlobContainersResult, GetFileSharesResult, HttpRequestMethod, GetLocationsResult, GetManagedDatabasesResult, CreateResourceGroupResult, GetBlobsResult, GetStorageAccountAccessKeyResult, AzureAccount, azureResource } from 'azurecore'; +import { AzureRestResponse, GetResourceGroupsResult, GetSubscriptionsResult, ResourceQueryResult, GetBlobContainersResult, GetFileSharesResult, HttpRequestMethod, GetLocationsResult, GetManagedDatabasesResult, CreateResourceGroupResult, GetBlobsResult, GetStorageAccountAccessKeyResult, AzureAccount, azureResource, AzureAccountProviderMetadata } from 'azurecore'; import { EOL } from 'os'; import * as nls from 'vscode-nls'; import { AppContext } from '../appContext'; @@ -16,6 +16,7 @@ import { AzureResourceServiceNames } from './constants'; import { IAzureResourceSubscriptionFilterService, IAzureResourceSubscriptionService } from './interfaces'; import { AzureResourceGroupService } from './providers/resourceGroup/resourceGroupService'; import { BlobServiceClient, StorageSharedKeyCredential } from '@azure/storage-blob'; +import providerSettings from '../account-provider/providerSettings'; const localize = nls.loadMessageBundle(); @@ -155,7 +156,8 @@ export async function getLocations(appContext: AppContext, account?: AzureAccoun try { const path = `/subscriptions/${subscription.id}/locations?api-version=2020-01-01`; - const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors); + const host = getProviderMetadataForAccount(account).settings.armResource.endpoint; + const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors, host); result.locations.push(...response.response.data.value); result.errors.push(...response.errors); } catch (err) { @@ -431,7 +433,8 @@ export async function makeHttpRequest(account: AzureAccount, subscription: azure export async function getManagedDatabases(account: AzureAccount, subscription: azureResource.AzureResourceSubscription, managedInstance: azureResource.AzureSqlManagedInstance, ignoreErrors: boolean): Promise { const path = `/subscriptions/${subscription.id}/resourceGroups/${managedInstance.resourceGroup}/providers/Microsoft.Sql/managedInstances/${managedInstance.name}/databases?api-version=2020-02-02-preview`; - const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors); + const host = getProviderMetadataForAccount(account).settings.armResource.endpoint; + const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors, host); return { databases: response?.response?.data?.value ?? [], errors: response.errors ? response.errors : [] @@ -440,7 +443,8 @@ export async function getManagedDatabases(account: AzureAccount, subscription: a export async function getBlobContainers(account: AzureAccount, subscription: azureResource.AzureResourceSubscription, storageAccount: azureResource.AzureGraphResource, ignoreErrors: boolean): Promise { const path = `/subscriptions/${subscription.id}/resourceGroups/${storageAccount.resourceGroup}/providers/Microsoft.Storage/storageAccounts/${storageAccount.name}/blobServices/default/containers?api-version=2019-06-01`; - const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors); + const host = getProviderMetadataForAccount(account).settings.armResource.endpoint; + const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors, host); return { blobContainers: response?.response?.data?.value ?? [], errors: response.errors ? response.errors : [] @@ -449,7 +453,8 @@ export async function getBlobContainers(account: AzureAccount, subscription: azu export async function getFileShares(account: AzureAccount, subscription: azureResource.AzureResourceSubscription, storageAccount: azureResource.AzureGraphResource, ignoreErrors: boolean): Promise { const path = `/subscriptions/${subscription.id}/resourceGroups/${storageAccount.resourceGroup}/providers/Microsoft.Storage/storageAccounts/${storageAccount.name}/fileServices/default/shares?api-version=2019-06-01`; - const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors); + const host = getProviderMetadataForAccount(account).settings.armResource.endpoint; + const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.GET, undefined, ignoreErrors, host); return { fileShares: response?.response?.data?.value ?? [], errors: response.errors ? response.errors : [] @@ -461,7 +466,8 @@ export async function createResourceGroup(account: AzureAccount, subscription: a const requestBody = { location: location }; - const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.PUT, requestBody, ignoreErrors); + const host = getProviderMetadataForAccount(account).settings.armResource.endpoint; + const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.PUT, requestBody, ignoreErrors, host); return { resourceGroup: response?.response?.data, errors: response.errors ? response.errors : [] @@ -470,7 +476,8 @@ export async function createResourceGroup(account: AzureAccount, subscription: a export async function getStorageAccountAccessKey(account: AzureAccount, subscription: azureResource.AzureResourceSubscription, storageAccount: azureResource.AzureGraphResource, ignoreErrors: boolean): Promise { const path = `/subscriptions/${subscription.id}/resourceGroups/${storageAccount.resourceGroup}/providers/Microsoft.Storage/storageAccounts/${storageAccount.name}/listKeys?api-version=2019-06-01`; - const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.POST, undefined, ignoreErrors); + const host = getProviderMetadataForAccount(account).settings.armResource.endpoint; + const response = await makeHttpRequest(account, subscription, path, HttpRequestMethod.POST, undefined, ignoreErrors, host); return { keyName1: response?.response?.data?.keys[0].value ?? '', keyName2: response?.response?.data?.keys[0].value ?? '', @@ -505,3 +512,11 @@ export async function getBlobs(account: AzureAccount, subscription: azureResourc } return result; } + +export function getProviderMetadataForAccount(account: AzureAccount): AzureAccountProviderMetadata { + const provider = providerSettings.find(provider => { + return account.properties.providerSettings.id === provider.metadata.id; + }); + + return provider.metadata; +} diff --git a/extensions/azurecore/src/azurecore.d.ts b/extensions/azurecore/src/azurecore.d.ts index 223d841f7d..e004fc18b0 100644 --- a/extensions/azurecore/src/azurecore.d.ts +++ b/extensions/azurecore/src/azurecore.d.ts @@ -306,6 +306,7 @@ declare module 'azurecore' { * @param region The region value */ getRegionDisplayName(region?: string): string; + getProviderMetadataForAccount(account: AzureAccount): AzureAccountProviderMetadata; provideResources(): azureResource.IAzureResourceProvider[]; runGraphQuery(account: AzureAccount, subscriptions: azureResource.AzureResourceSubscription[], ignoreErrors: boolean, query: string): Promise>; diff --git a/extensions/azurecore/src/extension.ts b/extensions/azurecore/src/extension.ts index e43821612a..dcb9690348 100644 --- a/extensions/azurecore/src/extension.ts +++ b/extensions/azurecore/src/extension.ts @@ -215,6 +215,9 @@ export async function activate(context: vscode.ExtensionContext): Promise(account: azurecore.AzureAccount, subscriptions: azurecore.azureResource.AzureResourceSubscription[], ignoreErrors: boolean, diff --git a/extensions/machine-learning/src/test/stubs.ts b/extensions/machine-learning/src/test/stubs.ts index 763d565d0f..cea74d97d7 100644 --- a/extensions/machine-learning/src/test/stubs.ts +++ b/extensions/machine-learning/src/test/stubs.ts @@ -55,6 +55,9 @@ export class AzurecoreApiStub implements azurecore.IExtension { getRegionDisplayName(_region?: string | undefined): string { throw new Error('Method not implemented.'); } + getProviderMetadataForAccount(_account: azurecore.AzureAccount): azurecore.AzureAccountProviderMetadata { + throw new Error('Method not implemented.'); + } provideResources(): azurecore.azureResource.IAzureResourceProvider[] { throw new Error('Method not implemented.'); } diff --git a/extensions/sql-migration/src/api/azure.ts b/extensions/sql-migration/src/api/azure.ts index c519bf2568..50eacfc415 100644 --- a/extensions/sql-migration/src/api/azure.ts +++ b/extensions/sql-migration/src/api/azure.ts @@ -9,6 +9,7 @@ import * as azurecore from 'azurecore'; import * as constants from '../constants/strings'; import { getSessionIdHeader } from './utils'; import { ProvisioningState } from '../models/migrationLocalStorage'; +import { URL } from 'url'; const ARM_MGMT_API_VERSION = '2021-04-01'; const SQL_VM_API_VERSION = '2021-11-01-preview'; @@ -35,7 +36,11 @@ export async function getSubscriptions(account: azdata.Account): Promise { const api = await getAzureCoreAPI(); const response = await api.getLocations(account, subscription, true); - const dataMigrationResourceProvider = (await api.makeAzureRestRequest(account, subscription, `/subscriptions/${subscription.id}/providers/Microsoft.DataMigration?api-version=${ARM_MGMT_API_VERSION}`, azurecore.HttpRequestMethod.GET)).response.data; + + const path = `/subscriptions/${subscription.id}/providers/Microsoft.DataMigration?api-version=${ARM_MGMT_API_VERSION}`; + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const dataMigrationResourceProvider = (await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host)).response.data; + const sqlMigratonResource = dataMigrationResourceProvider.resourceTypes.find((r: any) => r.resourceType === 'SqlMigrationServices'); const sqlMigrationResourceLocations = sqlMigratonResource.locations; @@ -106,7 +111,9 @@ export type SqlVMServer = { export async function getAvailableSqlVMs(account: azdata.Account, subscription: Subscription): Promise { const api = await getAzureCoreAPI(); const path = encodeURI(`/subscriptions/${subscription.id}/providers/Microsoft.SqlVirtualMachine/sqlVirtualMachines?api-version=${SQL_VM_API_VERSION}`); - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); + if (response.errors.length > 0) { throw new Error(response.errors.toString()); } @@ -154,7 +161,8 @@ export async function getSqlMigrationService(account: azdata.Account, subscripti export async function getSqlMigrationServiceById(account: azdata.Account, subscription: Subscription, sqlMigrationServiceId: string): Promise { const api = await getAzureCoreAPI(); const path = encodeURI(`${sqlMigrationServiceId}?api-version=${DMSV2_API_VERSION}`); - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } @@ -165,7 +173,8 @@ export async function getSqlMigrationServiceById(account: azdata.Account, subscr export async function getSqlMigrationServicesByResourceGroup(account: azdata.Account, subscription: Subscription, resouceGroupName: string): Promise { const api = await getAzureCoreAPI(); const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resouceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices?api-version=${DMSV2_API_VERSION}`); - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } @@ -179,7 +188,8 @@ export async function getSqlMigrationServicesByResourceGroup(account: azdata.Acc export async function getSqlMigrationServices(account: azdata.Account, subscription: Subscription): Promise { const api = await getAzureCoreAPI(); const path = encodeURI(`/subscriptions/${subscription.id}/providers/Microsoft.DataMigration/sqlMigrationServices?api-version=${DMSV2_API_VERSION}`); - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } @@ -196,17 +206,18 @@ export async function createSqlMigrationService(account: azdata.Account, subscri const requestBody = { 'location': regionName }; - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, undefined, getSessionIdHeader(sessionId)); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, host, getSessionIdHeader(sessionId)); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } - const asyncUrl = response.response.headers['azure-asyncoperation'] - .replace('https://management.azure.com/', ''); + const asyncUrl = response.response.headers['azure-asyncoperation']; + const asyncPath = asyncUrl.replace((new URL(asyncUrl)).origin + '/', ''); // path is everything after the hostname, e.g. the 'test' part of 'https://management.azure.com/test' const maxRetry = 24; let i = 0; for (i = 0; i < maxRetry; i++) { - const asyncResponse = await api.makeAzureRestRequest(account, subscription, asyncUrl, azurecore.HttpRequestMethod.GET, undefined, true); + const asyncResponse = await api.makeAzureRestRequest(account, subscription, asyncPath, azurecore.HttpRequestMethod.GET, undefined, true, host); const creationStatus = asyncResponse.response.data.status; if (creationStatus === ProvisioningState.Succeeded) { break; @@ -224,7 +235,8 @@ export async function createSqlMigrationService(account: azdata.Account, subscri export async function getSqlMigrationServiceAuthKeys(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string): Promise { const api = await getAzureCoreAPI(); const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}/ListAuthKeys?api-version=${DMSV2_API_VERSION}`); - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } @@ -241,8 +253,8 @@ export async function regenerateSqlMigrationServiceAuthKey(account: azdata.Accou 'location': regionName, 'keyName': keyName, }; - - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, requestBody, true); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, requestBody, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } @@ -267,7 +279,8 @@ export async function getStorageAccountAccessKeys(account: azdata.Account, subsc export async function getSqlMigrationServiceMonitoringData(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationService: string): Promise { const api = await getAzureCoreAPI(); const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationService}/listMonitoringData?api-version=${DMSV2_API_VERSION}`); - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } @@ -277,7 +290,8 @@ export async function getSqlMigrationServiceMonitoringData(account: azdata.Accou export async function startDatabaseMigration(account: azdata.Account, subscription: Subscription, regionName: string, targetServer: SqlManagedInstance | SqlVMServer, targetDatabaseName: string, requestBody: StartDatabaseMigrationRequest, sessionId: string): Promise { const api = await getAzureCoreAPI(); const path = encodeURI(`${targetServer.id}/providers/Microsoft.DataMigration/databaseMigrations/${targetDatabaseName}?api-version=${DMSV2_API_VERSION}`); - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, undefined, getSessionIdHeader(sessionId)); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, host, getSessionIdHeader(sessionId)); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } @@ -296,7 +310,8 @@ export async function getMigrationDetails(account: azdata.Account, subscription: : encodeURI(`${migrationId}?migrationOperationId=${migrationOperationId}&$expand=MigrationStatusDetails&api-version=${DMSV2_API_VERSION}`); const api = await getAzureCoreAPI(); - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, undefined, undefined); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host, undefined); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } @@ -307,8 +322,8 @@ export async function getMigrationDetails(account: azdata.Account, subscription: export async function getServiceMigrations(account: azdata.Account, subscription: Subscription, resourceId: string): Promise { const path = encodeURI(`${resourceId}/listMigrations?&api-version=${DMSV2_API_VERSION}`); const api = await getAzureCoreAPI(); - const response = await api.makeAzureRestRequest( - account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, undefined, undefined); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } @@ -320,21 +335,20 @@ export async function getMigrationTargetInstance(account: azdata.Account, subscr const targetServerId = getMigrationTargetId(migration); const path = encodeURI(`${targetServerId}?api-version=${SQL_MI_API_VERSION}`); const api = await getAzureCoreAPI(); - const response = await api.makeAzureRestRequest( - account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, undefined, undefined); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } return response.response.data; - - - return {}; } export async function getMigrationAsyncOperationDetails(account: azdata.Account, subscription: Subscription, url: string): Promise { const api = await getAzureCoreAPI(); - const response = await api.makeAzureRestRequest(account, subscription, url.replace('https://management.azure.com/', ''), azurecore.HttpRequestMethod.GET, undefined, true); + const path = url.replace((new URL(url)).origin + '/', ''); // path is everything after the hostname, e.g. the 'test' part of 'https://management.azure.com/test' + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, host); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } @@ -345,7 +359,8 @@ export async function startMigrationCutover(account: azdata.Account, subscriptio const api = await getAzureCoreAPI(); const path = encodeURI(`${migration.id}/cutover?api-version=${DMSV2_API_VERSION}`); const requestBody = { migrationOperationId: migration.properties.migrationOperationId }; - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, requestBody, true, undefined, undefined); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, requestBody, true, host, undefined); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } @@ -356,7 +371,8 @@ export async function stopMigration(account: azdata.Account, subscription: Subsc const api = await getAzureCoreAPI(); const path = encodeURI(`${migration.id}/cancel?api-version=${DMSV2_API_VERSION}`); const requestBody = { migrationOperationId: migration.properties.migrationOperationId }; - const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, requestBody, true, undefined, undefined); + const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint; + const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, requestBody, true, host, undefined); if (response.errors.length > 0) { throw new Error(response.errors.toString()); }