diff --git a/extensions/sql-migration/src/api/azure.ts b/extensions/sql-migration/src/api/azure.ts index ed5a5c6037..d4e674ee60 100644 --- a/extensions/sql-migration/src/api/azure.ts +++ b/extensions/sql-migration/src/api/azure.ts @@ -124,7 +124,7 @@ export type SqlVMServer = { }; export async function getAvailableSqlVMs(account: azdata.Account, subscription: Subscription, resourceGroup: azureResource.AzureResourceResourceGroup): Promise { const api = await getAzureCoreAPI(); - const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroup.name}/providers/Microsoft.SqlVirtualMachine/sqlVirtualMachines?api-version=2017-03-01-preview`; + const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroup.name}/providers/Microsoft.SqlVirtualMachine/sqlVirtualMachines?api-version=2017-03-01-preview`); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true); if (response.errors.length > 0) { throw new Error(response.errors.toString()); @@ -167,7 +167,7 @@ export async function getBlobs(account: azdata.Account, subscription: Subscripti export async function getSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string, sessionId: string): Promise { const api = await getAzureCoreAPI(); - const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=2020-09-01-preview`; + const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=2020-09-01-preview`); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, undefined, getSessionIdHeader(sessionId)); if (response.errors.length > 0) { throw new Error(response.errors.toString()); @@ -178,7 +178,7 @@ export async function getSqlMigrationService(account: azdata.Account, subscripti export async function getSqlMigrationServices(account: azdata.Account, subscription: Subscription, resouceGroupName: string, sessionId: string): Promise { const api = await getAzureCoreAPI(); - const path = `/subscriptions/${subscription.id}/resourceGroups/${resouceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices?api-version=2020-09-01-preview`; + const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resouceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices?api-version=2020-09-01-preview`); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, undefined, getSessionIdHeader(sessionId)); if (response.errors.length > 0) { throw new Error(response.errors.toString()); @@ -192,7 +192,7 @@ export async function getSqlMigrationServices(account: azdata.Account, subscript export async function createSqlMigrationService(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string, sessionId: string): Promise { const api = await getAzureCoreAPI(); - const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=2020-09-01-preview`; + const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=2020-09-01-preview`); const requestBody = { 'location': regionName }; @@ -221,7 +221,7 @@ export async function createSqlMigrationService(account: azdata.Account, subscri export async function getSqlMigrationServiceAuthKeys(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string, sessionId: string): Promise { const api = await getAzureCoreAPI(); - const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}/ListAuthKeys?api-version=2020-09-01-preview`; + const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}/ListAuthKeys?api-version=2020-09-01-preview`); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, undefined, getSessionIdHeader(sessionId)); if (response.errors.length > 0) { throw new Error(response.errors.toString()); @@ -234,7 +234,7 @@ export async function getSqlMigrationServiceAuthKeys(account: azdata.Account, su export async function regenerateSqlMigrationServiceAuthKey(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationServiceName: string, keyName: string, sessionId: string): Promise { const api = await getAzureCoreAPI(); - const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}/regenerateAuthKeys?api-version=2020-09-01-preview`; + const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}/regenerateAuthKeys?api-version=2020-09-01-preview`); const requestBody = { 'location': regionName, 'keyName': keyName, @@ -264,7 +264,7 @@ export async function getStorageAccountAccessKeys(account: azdata.Account, subsc export async function getSqlMigrationServiceMonitoringData(account: azdata.Account, subscription: Subscription, resourceGroupName: string, regionName: string, sqlMigrationService: string, sessionId: string): Promise { const api = await getAzureCoreAPI(); - const path = `/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationService}/monitoringData?api-version=2020-09-01-preview`; + const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationService}/monitoringData?api-version=2020-09-01-preview`); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, undefined, getSessionIdHeader(sessionId)); if (response.errors.length > 0) { throw new Error(response.errors.toString()); @@ -274,7 +274,7 @@ 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 = `${targetServer.id}/providers/Microsoft.DataMigration/databaseMigrations/${targetDatabaseName}?api-version=2020-09-01-preview`; + const path = encodeURI(`${targetServer.id}/providers/Microsoft.DataMigration/databaseMigrations/${targetDatabaseName}?api-version=2020-09-01-preview`); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.PUT, requestBody, true, undefined, getSessionIdHeader(sessionId)); if (response.errors.length > 0) { throw new Error(response.errors.toString()); @@ -287,27 +287,34 @@ export async function startDatabaseMigration(account: azdata.Account, subscripti }; } -export async function getMigrationStatus(account: azdata.Account, subscription: Subscription, migration: DatabaseMigration, sessionId: string, asyncUrl: string): Promise { +export async function getMigrationStatus(account: azdata.Account, subscription: Subscription, migration: DatabaseMigration, sessionId: string): Promise { + if (!migration.id) { + throw new Error('NullMigrationId'); + } + + const migrationOperationId = migration.properties?.migrationOperationId; + if (migrationOperationId === undefined && + migration.properties.provisioningState === ProvisioningState.Failed) { + return migration; + } + + const path = migrationOperationId === undefined + ? encodeURI(`${migration.id}?$expand=MigrationStatusDetails&api-version=2020-09-01-preview`) + : encodeURI(`${migration.id}?migrationOperationId=${migrationOperationId}&$expand=MigrationStatusDetails&api-version=2020-09-01-preview`); + const api = await getAzureCoreAPI(); - const migrationOperationId = getMigrationOperationId(migration, asyncUrl); - const path = `${migration.id}?migrationOperationId=${migrationOperationId}&$expand=MigrationStatusDetails&api-version=2020-09-01-preview`; const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, undefined, getSessionIdHeader(sessionId)); if (response.errors.length > 0) { throw new Error(response.errors.toString()); } - return response.response.data; -} -function getMigrationOperationId(migration: DatabaseMigration, asyncUrl: string): string { - // migrationOperationId may be undefined when provisioning has failed - // fall back to the operationId from the asyncUrl in the create migration response - if (migration.properties.migrationOperationId) { - return migration.properties.migrationOperationId; + const migrationUpdate: DatabaseMigration = response.response.data; + if (migration.properties) { + migrationUpdate.properties.sourceDatabaseName = migration.properties.sourceDatabaseName; + migrationUpdate.properties.backupConfiguration = migration.properties.backupConfiguration; } - return asyncUrl - ? vscode.Uri.parse(asyncUrl)?.path?.split('/').reverse()[0] - : ''; + return migrationUpdate; } export async function getMigrationAsyncOperationDetails(account: azdata.Account, subscription: Subscription, url: string, sessionId: string): Promise { @@ -321,7 +328,7 @@ export async function getMigrationAsyncOperationDetails(account: azdata.Account, export async function listMigrationsBySqlMigrationService(account: azdata.Account, subscription: Subscription, sqlMigrationService: SqlMigrationService, sessionId: string): Promise { const api = await getAzureCoreAPI(); - const path = `${sqlMigrationService.id}/listMigrations?$expand=MigrationStatusDetails&api-version=2020-09-01-preview`; + const path = encodeURI(`${sqlMigrationService.id}/listMigrations?$expand=MigrationStatusDetails&api-version=2020-09-01-preview`); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.GET, undefined, true, undefined, getSessionIdHeader(sessionId)); if (response.errors.length > 0) { throw new Error(response.errors.toString()); @@ -331,7 +338,7 @@ export async function listMigrationsBySqlMigrationService(account: azdata.Accoun export async function startMigrationCutover(account: azdata.Account, subscription: Subscription, migrationStatus: DatabaseMigration, sessionId: string): Promise { const api = await getAzureCoreAPI(); - const path = `${migrationStatus.id}/operations/${migrationStatus.properties.migrationOperationId}/cutover?api-version=2020-09-01-preview`; + const path = encodeURI(`${migrationStatus.id}/operations/${migrationStatus.properties.migrationOperationId}/cutover?api-version=2020-09-01-preview`); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, undefined, getSessionIdHeader(sessionId)); if (response.errors.length > 0) { throw new Error(response.errors.toString()); @@ -341,7 +348,7 @@ export async function startMigrationCutover(account: azdata.Account, subscriptio export async function stopMigration(account: azdata.Account, subscription: Subscription, migrationStatus: DatabaseMigration, sessionId: string): Promise { const api = await getAzureCoreAPI(); - const path = `${migrationStatus.id}/operations/${migrationStatus.properties.migrationOperationId}/cancel?api-version=2020-09-01-preview`; + const path = encodeURI(`${migrationStatus.id}/operations/${migrationStatus.properties.migrationOperationId}/cancel?api-version=2020-09-01-preview`); const response = await api.makeAzureRestRequest(account, subscription, path, azurecore.HttpRequestMethod.POST, undefined, true, undefined, getSessionIdHeader(sessionId)); if (response.errors.length > 0) { throw new Error(response.errors.toString()); diff --git a/extensions/sql-migration/src/api/utils.ts b/extensions/sql-migration/src/api/utils.ts index 331a6df9f9..0fcf6d7875 100644 --- a/extensions/sql-migration/src/api/utils.ts +++ b/extensions/sql-migration/src/api/utils.ts @@ -95,27 +95,27 @@ export function filterMigrations(databaseMigrations: MigrationContext[], statusF filteredMigration = databaseMigrations; } else if (statusFilter === AdsMigrationStatus.ONGOING) { filteredMigration = databaseMigrations.filter((value) => { - const status = value.migrationContext.properties.migrationStatus; - const provisioning = value.migrationContext.properties.provisioningState; + const status = value.migrationContext.properties?.migrationStatus; + const provisioning = value.migrationContext.properties?.provisioningState; return status === MigrationStatus.InProgress || status === MigrationStatus.Creating || provisioning === MigrationStatus.Creating; }); } else if (statusFilter === AdsMigrationStatus.SUCCEEDED) { filteredMigration = databaseMigrations.filter((value) => { - const status = value.migrationContext.properties.migrationStatus; + const status = value.migrationContext.properties?.migrationStatus; return status === MigrationStatus.Succeeded; }); } else if (statusFilter === AdsMigrationStatus.FAILED) { filteredMigration = databaseMigrations.filter((value) => { - const status = value.migrationContext.properties.migrationStatus; - const provisioning = value.migrationContext.properties.provisioningState; + const status = value.migrationContext.properties?.migrationStatus; + const provisioning = value.migrationContext.properties?.provisioningState; return status === MigrationStatus.Failed || provisioning === ProvisioningState.Failed; }); } else if (statusFilter === AdsMigrationStatus.COMPLETING) { filteredMigration = databaseMigrations.filter((value) => { - const status = value.migrationContext.properties.migrationStatus; + const status = value.migrationContext.properties?.migrationStatus; return status === MigrationStatus.Completing; }); } diff --git a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialogModel.ts b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialogModel.ts index 8eb74e98f9..9539cab6ad 100644 --- a/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialogModel.ts +++ b/extensions/sql-migration/src/dialog/migrationCutover/migrationCutoverDialogModel.ts @@ -18,27 +18,25 @@ export class MigrationCutoverDialogModel { public async fetchStatus(): Promise { if (this._migration.asyncUrl) { - this.migrationOpStatus = (await getMigrationAsyncOperationDetails( + this.migrationOpStatus = await getMigrationAsyncOperationDetails( this._migration.azureAccount, this._migration.subscription, this._migration.asyncUrl, - this._migration.sessionId! - )); + this._migration.sessionId!); } - this.migrationStatus = (await getMigrationStatus( + + this.migrationStatus = await getMigrationStatus( this._migration.azureAccount, this._migration.subscription, this._migration.migrationContext, - this._migration.sessionId!, - this._migration.asyncUrl - )); + this._migration.sessionId!); sendSqlMigrationActionEvent( TelemetryViews.MigrationCutoverDialog, TelemetryAction.MigrationStatus, { 'sessionId': this._migration.sessionId!, - 'migrationStatus': this.migrationStatus.properties.migrationStatus + 'migrationStatus': this.migrationStatus.properties?.migrationStatus }, {} ); diff --git a/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts b/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts index 557f796bdc..10fa06c4e2 100644 --- a/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts +++ b/extensions/sql-migration/src/dialog/migrationStatus/migrationStatusDialog.ts @@ -316,7 +316,7 @@ export class MigrationStatusDialog { this._searchBox.value!); migrations.sort((m1, m2) => { - return new Date(m1.migrationContext.properties.startedOn) > new Date(m2.migrationContext.properties.startedOn) ? -1 : 1; + return new Date(m1.migrationContext.properties?.startedOn) > new Date(m2.migrationContext.properties?.startedOn) ? -1 : 1; }); const data: azdata.DeclarativeTableCellValue[][] = migrations.map((migration, index) => { diff --git a/extensions/sql-migration/src/models/migrationLocalStorage.ts b/extensions/sql-migration/src/models/migrationLocalStorage.ts index 10589c262f..4e83af1985 100644 --- a/extensions/sql-migration/src/models/migrationLocalStorage.ts +++ b/extensions/sql-migration/src/models/migrationLocalStorage.ts @@ -23,14 +23,11 @@ export class MigrationLocalStorage { const migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || []; for (let i = 0; i < migrationMementos.length; i++) { const migration = migrationMementos[i]; + migration.migrationContext = this.removeMigrationSecrets(migration.migrationContext); migration.sessionId = migration.sessionId ?? undefinedSessionId; if (migration.sourceConnectionProfile.serverName === connectionProfile.serverName) { if (refreshStatus) { try { - const autoCutoverConfiguration = migration.migrationContext.properties.autoCutoverConfiguration; - const backupConfiguration = migration.migrationContext.properties.backupConfiguration; - const sourceDatabase = migration.migrationContext.properties.sourceDatabaseName; - await this.refreshMigrationAzureAccount(migration); if (migration.asyncUrl) { @@ -38,28 +35,23 @@ export class MigrationLocalStorage { migration.azureAccount, migration.subscription, migration.asyncUrl, - migration.sessionId! - ); - - migration.migrationContext = await getMigrationStatus( - migration.azureAccount, - migration.subscription, - migration.migrationContext, - migration.sessionId!, - migration.asyncUrl - ); - - migration.migrationContext.properties.sourceDatabaseName = sourceDatabase; - migration.migrationContext.properties.backupConfiguration = backupConfiguration; - migration.migrationContext.properties.autoCutoverConfiguration = autoCutoverConfiguration; + migration.sessionId!); } + + migration.migrationContext = await getMigrationStatus( + migration.azureAccount, + migration.subscription, + migration.migrationContext, + migration.sessionId!); } catch (e) { // Keeping only valid migrations in cache. Clearing all the migrations which return ResourceDoesNotExit error. - if (e.message === 'ResourceDoesNotExist') { - continue; - } else { - console.log(e); + switch (e.message) { + case 'ResourceDoesNotExist': + case 'NullMigrationId': + continue; + default: + console.log(e); } } } @@ -99,7 +91,7 @@ export class MigrationLocalStorage { migrationMementos = migrationMementos.filter(m => m.migrationContext.id !== migrationContext.id); migrationMementos.push({ sourceConnectionProfile: connectionProfile, - migrationContext: migrationContext, + migrationContext: this.removeMigrationSecrets(migrationContext), targetManagedInstance: targetMI, subscription: subscription, azureAccount: azureAccount, @@ -116,6 +108,23 @@ export class MigrationLocalStorage { public static clearMigrations() { this.context.globalState.update(this.mementoToken, ([] as MigrationContext[])); } + + public static removeMigrationSecrets(migration: DatabaseMigration): DatabaseMigration { + // remove secrets from migration context + if (migration.properties.sourceSqlConnection?.password) { + migration.properties.sourceSqlConnection.password = ''; + } + if (migration.properties.backupConfiguration?.sourceLocation?.fileShare?.password) { + migration.properties.backupConfiguration.sourceLocation.fileShare.password = ''; + } + if (migration.properties.backupConfiguration?.sourceLocation?.azureBlob?.accountKey) { + migration.properties.backupConfiguration.sourceLocation.azureBlob.accountKey = ''; + } + if (migration.properties.backupConfiguration?.targetLocation?.accountKey) { + migration.properties.backupConfiguration.targetLocation.accountKey = ''; + } + return migration; + } } export interface MigrationContext {