mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-09 01:32:34 -05:00
Dev/brih/feature/switch ads to portal context (#18963)
* Add CodeQL Analysis workflow (#10195) * Add CodeQL Analysis workflow * Fix path * dashboard refactor * update version, readme, minor ui changes * fix merge issue * Revert "Add CodeQL Analysis workflow (#10195)" This reverts commit fe98d586cd75be4758ac544649bb4983accf4acd. * fix context switching issue * fix resource id parsing error and mi api version * mv refresh btn, rm autorefresh, align cards * remove missed autorefresh code * improve error handling and messages * fix typos * remove duplicate/unnecessary _populate* calls * change clear configuration button text * remove confusing watermark text * add stale account handling Co-authored-by: Justin Hutchings <jhutchings1@users.noreply.github.com>
This commit is contained in:
@@ -2,11 +2,13 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as vscode from 'vscode';
|
||||
import { azureResource } from 'azureResource';
|
||||
import { logError, sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews } from '../telemtery';
|
||||
import { DatabaseMigration, SqlMigrationService, SqlManagedInstance, getMigrationStatus, AzureAsyncOperationResource, getMigrationAsyncOperationDetails, SqlVMServer, getSubscriptions } from '../api/azure';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as azurecore from 'azurecore';
|
||||
import { azureResource } from 'azureResource';
|
||||
import { DatabaseMigration, SqlMigrationService, getSubscriptions, getServiceMigrations } from '../api/azure';
|
||||
import { deepClone } from '../api/utils';
|
||||
import * as loc from '../constants/strings';
|
||||
|
||||
export class MigrationLocalStorage {
|
||||
private static context: vscode.ExtensionContext;
|
||||
@@ -16,161 +18,75 @@ export class MigrationLocalStorage {
|
||||
MigrationLocalStorage.context = context;
|
||||
}
|
||||
|
||||
public static async getMigrationsBySourceConnections(connectionProfile: azdata.connection.ConnectionProfile, refreshStatus?: boolean): Promise<MigrationContext[]> {
|
||||
const undefinedSessionId = '{undefined}';
|
||||
const result: MigrationContext[] = [];
|
||||
const validMigrations: MigrationContext[] = [];
|
||||
const startTime = new Date().toString();
|
||||
// fetch saved migrations
|
||||
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) {
|
||||
// refresh migration status
|
||||
if (refreshStatus) {
|
||||
try {
|
||||
await this.refreshMigrationAzureAccount(migration);
|
||||
|
||||
if (migration.asyncUrl) {
|
||||
migration.asyncOperationResult = await getMigrationAsyncOperationDetails(
|
||||
migration.azureAccount,
|
||||
migration.subscription,
|
||||
migration.asyncUrl,
|
||||
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.
|
||||
switch (e.message) {
|
||||
case 'ResourceDoesNotExist':
|
||||
case 'NullMigrationId':
|
||||
continue;
|
||||
default:
|
||||
logError(TelemetryViews.MigrationLocalStorage, 'MigrationBySourceConnectionError', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
result.push(migration);
|
||||
}
|
||||
validMigrations.push(migration);
|
||||
public static async getMigrationServiceContext(): Promise<MigrationServiceContext> {
|
||||
const connectionProfile = await azdata.connection.getCurrentConnection();
|
||||
if (connectionProfile) {
|
||||
const serverContextKey = `${this.mementoToken}.${connectionProfile.serverName}.serviceContext`;
|
||||
return deepClone(await this.context.globalState.get(serverContextKey)) || {};
|
||||
}
|
||||
|
||||
await this.context.globalState.update(this.mementoToken, validMigrations);
|
||||
|
||||
sendSqlMigrationActionEvent(
|
||||
TelemetryViews.MigrationLocalStorage,
|
||||
TelemetryAction.Done,
|
||||
{
|
||||
'startTime': startTime,
|
||||
'endTime': new Date().toString()
|
||||
},
|
||||
{
|
||||
'migrationCount': migrationMementos.length
|
||||
}
|
||||
);
|
||||
|
||||
// only save updated migration context
|
||||
if (refreshStatus) {
|
||||
const migrations: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
|
||||
validMigrations.forEach(migration => {
|
||||
const idx = migrations.findIndex(m => m.migrationContext.id === migration.migrationContext.id);
|
||||
if (idx > -1) {
|
||||
migrations[idx] = migration;
|
||||
}
|
||||
});
|
||||
|
||||
// check global state for migrations count mismatch, avoid saving
|
||||
// state if the count has changed when a migration may have been added
|
||||
const current: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
|
||||
if (current.length === migrations.length) {
|
||||
await this.context.globalState.update(this.mementoToken, migrations);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return {};
|
||||
}
|
||||
|
||||
public static async refreshMigrationAzureAccount(migration: MigrationContext): Promise<void> {
|
||||
if (migration.azureAccount.isStale) {
|
||||
public static async saveMigrationServiceContext(serviceContext: MigrationServiceContext): Promise<void> {
|
||||
const connectionProfile = await azdata.connection.getCurrentConnection();
|
||||
if (connectionProfile) {
|
||||
const serverContextKey = `${this.mementoToken}.${connectionProfile.serverName}.serviceContext`;
|
||||
return await this.context.globalState.update(serverContextKey, deepClone(serviceContext));
|
||||
}
|
||||
}
|
||||
|
||||
public static async refreshMigrationAzureAccount(serviceContext: MigrationServiceContext, migration: DatabaseMigration): Promise<void> {
|
||||
if (serviceContext.azureAccount?.isStale) {
|
||||
const accounts = await azdata.accounts.getAllAccounts();
|
||||
const account = accounts.find(a => !a.isStale && a.key.accountId === migration.azureAccount.key.accountId);
|
||||
const account = accounts.find(a => !a.isStale && a.key.accountId === serviceContext.azureAccount?.key.accountId);
|
||||
if (account) {
|
||||
const subscriptions = await getSubscriptions(account);
|
||||
const subscription = subscriptions.find(s => s.id === migration.subscription.id);
|
||||
const subscription = subscriptions.find(s => s.id === serviceContext.subscription?.id);
|
||||
if (subscription) {
|
||||
migration.azureAccount = account;
|
||||
serviceContext.azureAccount = account;
|
||||
await this.saveMigrationServiceContext(serviceContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async saveMigration(
|
||||
connectionProfile: azdata.connection.ConnectionProfile,
|
||||
migrationContext: DatabaseMigration,
|
||||
targetMI: SqlManagedInstance | SqlVMServer,
|
||||
azureAccount: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
controller: SqlMigrationService,
|
||||
asyncURL: string,
|
||||
sessionId: string): Promise<void> {
|
||||
try {
|
||||
let migrationMementos: MigrationContext[] = this.context.globalState.get(this.mementoToken) || [];
|
||||
migrationMementos = migrationMementos.filter(m => m.migrationContext.id !== migrationContext.id);
|
||||
migrationMementos.push({
|
||||
sourceConnectionProfile: connectionProfile,
|
||||
migrationContext: this.removeMigrationSecrets(migrationContext),
|
||||
targetManagedInstance: targetMI,
|
||||
subscription: subscription,
|
||||
azureAccount: azureAccount,
|
||||
controller: controller,
|
||||
asyncUrl: asyncURL,
|
||||
sessionId: sessionId
|
||||
});
|
||||
await this.context.globalState.update(this.mementoToken, migrationMementos);
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.MigrationLocalStorage, 'CantSaveMigration', e);
|
||||
}
|
||||
}
|
||||
|
||||
public static async clearMigrations(): Promise<void> {
|
||||
await 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 {
|
||||
sourceConnectionProfile: azdata.connection.ConnectionProfile,
|
||||
migrationContext: DatabaseMigration,
|
||||
targetManagedInstance: SqlManagedInstance | SqlVMServer,
|
||||
azureAccount: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
controller: SqlMigrationService,
|
||||
asyncUrl: string,
|
||||
asyncOperationResult?: AzureAsyncOperationResource,
|
||||
sessionId?: string
|
||||
export function isServiceContextValid(serviceContext: MigrationServiceContext): boolean {
|
||||
return (
|
||||
serviceContext.azureAccount?.isStale === false &&
|
||||
serviceContext.location?.id !== undefined &&
|
||||
serviceContext.migrationService?.id !== undefined &&
|
||||
serviceContext.resourceGroup?.id !== undefined &&
|
||||
serviceContext.subscription?.id !== undefined &&
|
||||
serviceContext.tenant?.id !== undefined
|
||||
);
|
||||
}
|
||||
|
||||
export async function getSelectedServiceStatus(): Promise<string> {
|
||||
const serviceContext = await MigrationLocalStorage.getMigrationServiceContext();
|
||||
const serviceName = serviceContext?.migrationService?.name;
|
||||
return serviceName && isServiceContextValid(serviceContext)
|
||||
? loc.MIGRATION_SERVICE_SERVICE_PROMPT(serviceName)
|
||||
: loc.MIGRATION_SERVICE_SELECT_SERVICE_PROMPT;
|
||||
}
|
||||
|
||||
export async function getCurrentMigrations(): Promise<DatabaseMigration[]> {
|
||||
const serviceContext = await MigrationLocalStorage.getMigrationServiceContext();
|
||||
return isServiceContextValid(serviceContext)
|
||||
? await getServiceMigrations(
|
||||
serviceContext.azureAccount!,
|
||||
serviceContext.subscription!,
|
||||
serviceContext.migrationService?.id!)
|
||||
: [];
|
||||
}
|
||||
|
||||
export interface MigrationServiceContext {
|
||||
azureAccount?: azdata.Account,
|
||||
tenant?: azurecore.Tenant,
|
||||
subscription?: azureResource.AzureResourceSubscription,
|
||||
location?: azureResource.AzureLocation,
|
||||
resourceGroup?: azureResource.AzureResourceResourceGroup,
|
||||
migrationService?: SqlMigrationService,
|
||||
}
|
||||
|
||||
export enum MigrationStatus {
|
||||
|
||||
@@ -8,9 +8,8 @@ import { azureResource } from 'azureResource';
|
||||
import * as azurecore from 'azurecore';
|
||||
import * as vscode from 'vscode';
|
||||
import * as mssql from 'mssql';
|
||||
import { getAvailableManagedInstanceProducts, getAvailableStorageAccounts, getBlobContainers, getFileShares, getSqlMigrationServices, getSubscriptions, SqlMigrationService, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, getAvailableSqlVMs, SqlVMServer, getLocations, getLocationDisplayName, getSqlManagedInstanceDatabases, getBlobs, sortResourceArrayByName, getFullResourceGroupFromId, getResourceGroupFromId, getResourceGroups } from '../api/azure';
|
||||
import { getAvailableManagedInstanceProducts, getAvailableStorageAccounts, getBlobContainers, getFileShares, getSqlMigrationServices, getSubscriptions, SqlMigrationService, SqlManagedInstance, startDatabaseMigration, StartDatabaseMigrationRequest, StorageAccount, getAvailableSqlVMs, SqlVMServer, getLocations, getLocationDisplayName, getSqlManagedInstanceDatabases, getBlobs, sortResourceArrayByName, getFullResourceGroupFromId, getResourceGroupFromId, getResourceGroups, getSqlMigrationServicesByResourceGroup } from '../api/azure';
|
||||
import * as constants from '../constants/strings';
|
||||
import { MigrationLocalStorage } from './migrationLocalStorage';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { sendSqlMigrationActionEvent, TelemetryAction, TelemetryViews, logError } from '../telemtery';
|
||||
@@ -58,6 +57,12 @@ export enum NetworkContainerType {
|
||||
NETWORK_SHARE
|
||||
}
|
||||
|
||||
export enum FileStorageType {
|
||||
FileShare = 'FileShare',
|
||||
AzureBlob = 'AzureBlob',
|
||||
None = 'None',
|
||||
}
|
||||
|
||||
export enum Page {
|
||||
DatabaseSelector,
|
||||
SKURecommendation,
|
||||
@@ -826,8 +831,10 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
}
|
||||
accountValues = this._azureAccounts.map((account): azdata.CategoryValue => {
|
||||
return {
|
||||
displayName: account.displayInfo.displayName,
|
||||
name: account.displayInfo.userId
|
||||
name: account.displayInfo.userId,
|
||||
displayName: account.isStale
|
||||
? constants.ACCOUNT_CREDENTIALS_REFRESH(account.displayInfo.displayName)
|
||||
: account.displayInfo.displayName
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
@@ -871,7 +878,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
public async getSubscriptionsDropdownValues(): Promise<azdata.CategoryValue[]> {
|
||||
let subscriptionsValues: azdata.CategoryValue[] = [];
|
||||
try {
|
||||
if (this._azureAccount) {
|
||||
if (this._azureAccount?.isStale === false) {
|
||||
this._subscriptions = await getSubscriptions(this._azureAccount);
|
||||
} else {
|
||||
this._subscriptions = [];
|
||||
@@ -1471,7 +1478,12 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
let sqlMigrationServiceValues: azdata.CategoryValue[] = [];
|
||||
try {
|
||||
if (this._azureAccount && subscription && resourceGroupName && this._targetServerInstance) {
|
||||
this._sqlMigrationServices = (await getSqlMigrationServices(this._azureAccount, subscription)).filter(sms => sms.location.toLowerCase() === this._targetServerInstance.location.toLowerCase() && sms.properties.resourceGroup.toLowerCase() === resourceGroupName.toLowerCase());
|
||||
const services = await getSqlMigrationServicesByResourceGroup(
|
||||
this._azureAccount,
|
||||
subscription,
|
||||
resourceGroupName?.toLowerCase());
|
||||
const targetLoc = this._targetServerInstance.location.toLowerCase();
|
||||
this._sqlMigrationServices = services.filter(sms => sms.location.toLowerCase() === targetLoc);
|
||||
} else {
|
||||
this._sqlMigrationServices = [];
|
||||
}
|
||||
@@ -1545,6 +1557,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
requestBody.properties.backupConfiguration = {
|
||||
targetLocation: undefined!,
|
||||
sourceLocation: {
|
||||
fileStorageType: 'AzureBlob',
|
||||
azureBlob: {
|
||||
storageAccountResourceId: this._databaseBackup.blobs[i].storageAccount.id,
|
||||
accountKey: this._databaseBackup.blobs[i].storageKey,
|
||||
@@ -1567,6 +1580,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
accountKey: this._databaseBackup.networkShares[i].storageKey,
|
||||
},
|
||||
sourceLocation: {
|
||||
fileStorageType: 'FileShare',
|
||||
fileShare: {
|
||||
path: this._databaseBackup.networkShares[i].networkShareLocation,
|
||||
username: this._databaseBackup.networkShares[i].windowsUser,
|
||||
@@ -1584,8 +1598,8 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
this._targetServerInstance,
|
||||
this._targetDatabaseNames[i],
|
||||
requestBody,
|
||||
this._sessionId
|
||||
);
|
||||
this._sessionId);
|
||||
|
||||
response.databaseMigration.properties.sourceDatabaseName = this._databasesForMigration[i];
|
||||
response.databaseMigration.properties.backupConfiguration = requestBody.properties.backupConfiguration!;
|
||||
response.databaseMigration.properties.offlineConfiguration = requestBody.properties.offlineConfiguration!;
|
||||
@@ -1621,22 +1635,18 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
}
|
||||
);
|
||||
|
||||
await MigrationLocalStorage.saveMigration(
|
||||
currentConnection!,
|
||||
response.databaseMigration,
|
||||
this._targetServerInstance,
|
||||
this._azureAccount,
|
||||
this._targetSubscription,
|
||||
this._sqlMigrationService!,
|
||||
response.asyncUrl,
|
||||
this._sessionId
|
||||
);
|
||||
void vscode.window.showInformationMessage(localize("sql.migration.starting.migration.message", 'Starting migration for database {0} to {1} - {2}', this._databasesForMigration[i], this._targetServerInstance.name, this._targetDatabaseNames[i]));
|
||||
void vscode.window.showInformationMessage(
|
||||
localize(
|
||||
"sql.migration.starting.migration.message",
|
||||
'Starting migration for database {0} to {1} - {2}',
|
||||
this._databasesForMigration[i],
|
||||
this._targetServerInstance.name,
|
||||
this._targetDatabaseNames[i]));
|
||||
}
|
||||
} catch (e) {
|
||||
void vscode.window.showErrorMessage(
|
||||
localize('sql.migration.starting.migration.error', "An error occurred while starting the migration: '{0}'", e.message));
|
||||
console.log(e);
|
||||
logError(TelemetryViews.MigrationLocalStorage, 'StartMigrationFailed', e);
|
||||
}
|
||||
finally {
|
||||
// kill existing data collection if user start migration
|
||||
@@ -1718,7 +1728,7 @@ export class MigrationStateModel implements Model, vscode.Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public loadSavedInfo(): Boolean {
|
||||
public async loadSavedInfo(): Promise<Boolean> {
|
||||
try {
|
||||
this._targetType = this.savedInfo.migrationTargetType || undefined!;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user