mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-17 01:25:36 -05:00
Add SQL DB offline migration wizard experience (#20403)
* sql db wizard with target selection * add database table selection * add sqldb to service and IR page * Code complete * navigation bug fixes * fix target db selection * improve sqldb error and status reporting * fix error count bug * remove table status inference * address review feedback * update resource strings and content * fix migraton status string, use localized value * fix ux navigation issues * fix back/fwd w/o changes from changing data
This commit is contained in:
@@ -8,12 +8,12 @@ import * as azdata from 'azdata';
|
||||
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';
|
||||
const SQL_MI_API_VERSION = '2021-11-01-preview';
|
||||
const SQL_SQLDB_API_VERSION = '2021-11-01-preview';
|
||||
const DMSV2_API_VERSION = '2022-03-30-preview';
|
||||
|
||||
async function getAzureCoreAPI(): Promise<azurecore.IExtension> {
|
||||
@@ -93,6 +93,100 @@ export async function getAvailableSqlServers(account: azdata.Account, subscripti
|
||||
return result.resources;
|
||||
}
|
||||
|
||||
export interface SKU {
|
||||
name: string,
|
||||
tier: 'GeneralPurpose' | 'BusinessCritical',
|
||||
family: string,
|
||||
capacity: number,
|
||||
}
|
||||
|
||||
export interface AzureSqlDatabase {
|
||||
id: string,
|
||||
name: string,
|
||||
location: string,
|
||||
tags: any,
|
||||
type: string,
|
||||
sku: SKU,
|
||||
kind: string,
|
||||
properties: {
|
||||
collation: string,
|
||||
maxSizeBytes: number,
|
||||
status: string,
|
||||
databaseId: string,
|
||||
creationDate: string,
|
||||
currentServiceObjectiveName: string,
|
||||
requestedServiceObjectiveName: string,
|
||||
defaultSecondaryLocation: string,
|
||||
catalogCollation: string,
|
||||
zoneRedundant: boolean,
|
||||
earliestRestoreDate: string,
|
||||
readScale: string,
|
||||
currentSku: SKU,
|
||||
currentBackupStorageRedundancy: string,
|
||||
requestedBackupStorageRedundancy: string,
|
||||
maintenanceConfigurationId: string,
|
||||
isLedgerOn: boolean
|
||||
isInfraEncryptionEnabled: boolean,
|
||||
licenseType: string,
|
||||
maxLogSizeBytes: number,
|
||||
},
|
||||
}
|
||||
|
||||
export interface ServerAdministrators {
|
||||
administratorType: string,
|
||||
azureADOnlyAuthentication: boolean,
|
||||
login: string,
|
||||
principalType: string,
|
||||
sid: string,
|
||||
tenantId: string,
|
||||
}
|
||||
|
||||
export interface PrivateEndpointProperty {
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export interface PrivateLinkServiceConnectionStateProperty {
|
||||
status: string;
|
||||
description: string;
|
||||
readonly actionsRequired?: string;
|
||||
}
|
||||
|
||||
export interface PrivateEndpointConnectionProperties {
|
||||
groupIds: string[];
|
||||
privateEndpoint?: PrivateEndpointProperty;
|
||||
privateLinkServiceConnectionState?: PrivateLinkServiceConnectionStateProperty;
|
||||
readonly provisioningState?: string;
|
||||
}
|
||||
|
||||
export interface ServerPrivateEndpointConnection {
|
||||
readonly id?: string;
|
||||
readonly properties?: PrivateEndpointConnectionProperties;
|
||||
}
|
||||
|
||||
export interface AzureSqlDatabaseServer {
|
||||
id: string,
|
||||
name: string,
|
||||
kind: string,
|
||||
location: string,
|
||||
tags?: { [propertyName: string]: string; };
|
||||
type: string,
|
||||
// sku: SKU,
|
||||
// subscriptionId: string,
|
||||
// tenantId: string,
|
||||
// fullName: string,
|
||||
properties: {
|
||||
administratorLogin: string,
|
||||
administrators: ServerAdministrators,
|
||||
fullyQualifiedDomainName: string,
|
||||
minimalTlsVersion: string,
|
||||
privateEndpointConnections: ServerPrivateEndpointConnection[],
|
||||
publicNetworkAccess: string,
|
||||
restrictOutboundNetworkAccess: string,
|
||||
state: string,
|
||||
version: string,
|
||||
},
|
||||
}
|
||||
|
||||
export type SqlVMServer = {
|
||||
properties: {
|
||||
virtualMachineResourceId: string,
|
||||
@@ -108,6 +202,31 @@ export type SqlVMServer = {
|
||||
tenantId: string,
|
||||
subscriptionId: string
|
||||
};
|
||||
|
||||
export async function getAvailableSqlDatabaseServers(account: azdata.Account, subscription: Subscription): Promise<AzureSqlDatabaseServer[]> {
|
||||
const api = await getAzureCoreAPI();
|
||||
const path = encodeURI(`/subscriptions/${subscription.id}/providers/Microsoft.Sql/servers?api-version=${SQL_SQLDB_API_VERSION}`);
|
||||
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());
|
||||
}
|
||||
sortResourceArrayByName(response.response.data.value);
|
||||
return response.response.data.value;
|
||||
}
|
||||
|
||||
export async function getAvailableSqlDatabases(account: azdata.Account, subscription: Subscription, resourceGroupName: string, serverName: string): Promise<AzureSqlDatabase[]> {
|
||||
const api = await getAzureCoreAPI();
|
||||
const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.Sql/servers/${serverName}/databases?api-version=${SQL_SQLDB_API_VERSION}`);
|
||||
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());
|
||||
}
|
||||
sortResourceArrayByName(response.response.data.value);
|
||||
return response.response.data.value;
|
||||
}
|
||||
|
||||
export async function getAvailableSqlVMs(account: azdata.Account, subscription: Subscription): Promise<SqlVMServer[]> {
|
||||
const api = await getAzureCoreAPI();
|
||||
const path = encodeURI(`/subscriptions/${subscription.id}/providers/Microsoft.SqlVirtualMachine/sqlVirtualMachines?api-version=${SQL_VM_API_VERSION}`);
|
||||
@@ -203,10 +322,10 @@ 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<SqlMigrationService> {
|
||||
const api = await getAzureCoreAPI();
|
||||
const path = encodeURI(`/subscriptions/${subscription.id}/resourceGroups/${resourceGroupName}/providers/Microsoft.DataMigration/sqlMigrationServices/${sqlMigrationServiceName}?api-version=${DMSV2_API_VERSION}`);
|
||||
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
|
||||
const requestBody = {
|
||||
'location': regionName
|
||||
};
|
||||
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());
|
||||
@@ -219,9 +338,9 @@ export async function createSqlMigrationService(account: azdata.Account, subscri
|
||||
for (i = 0; i < maxRetry; i++) {
|
||||
const asyncResponse = await api.makeAzureRestRequest(account, subscription, asyncPath, azurecore.HttpRequestMethod.GET, undefined, true, host);
|
||||
const creationStatus = asyncResponse.response.data.status;
|
||||
if (creationStatus === ProvisioningState.Succeeded) {
|
||||
if (creationStatus === constants.ProvisioningState.Succeeded) {
|
||||
break;
|
||||
} else if (creationStatus === ProvisioningState.Failed) {
|
||||
} else if (creationStatus === constants.ProvisioningState.Failed) {
|
||||
throw new Error(asyncResponse.errors.toString());
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 5000)); //adding 5 sec delay before getting creation status
|
||||
@@ -287,7 +406,15 @@ export async function getSqlMigrationServiceMonitoringData(account: azdata.Accou
|
||||
return response.response.data;
|
||||
}
|
||||
|
||||
export async function startDatabaseMigration(account: azdata.Account, subscription: Subscription, regionName: string, targetServer: SqlManagedInstance | SqlVMServer, targetDatabaseName: string, requestBody: StartDatabaseMigrationRequest, sessionId: string): Promise<StartDatabaseMigrationResponse> {
|
||||
export async function startDatabaseMigration(
|
||||
account: azdata.Account,
|
||||
subscription: Subscription,
|
||||
regionName: string,
|
||||
targetServer: SqlManagedInstance | SqlVMServer | AzureSqlDatabaseServer,
|
||||
targetDatabaseName: string,
|
||||
requestBody: StartDatabaseMigrationRequest,
|
||||
sessionId: string): Promise<StartDatabaseMigrationResponse> {
|
||||
|
||||
const api = await getAzureCoreAPI();
|
||||
const path = encodeURI(`${targetServer.id}/providers/Microsoft.DataMigration/databaseMigrations/${targetDatabaseName}?api-version=${DMSV2_API_VERSION}`);
|
||||
const host = api.getProviderMetadataForAccount(account).settings.armResource?.endpoint;
|
||||
@@ -438,10 +565,10 @@ export interface SqlMigrationServiceProperties {
|
||||
}
|
||||
|
||||
export interface SqlMigrationService {
|
||||
properties: SqlMigrationServiceProperties;
|
||||
location: string;
|
||||
id: string;
|
||||
name: string;
|
||||
location: string;
|
||||
properties: SqlMigrationServiceProperties;
|
||||
error: {
|
||||
code: string,
|
||||
message: string
|
||||
@@ -485,14 +612,29 @@ export interface StartDatabaseMigrationRequest {
|
||||
},
|
||||
sourceLocation?: SourceLocation
|
||||
},
|
||||
sourceSqlConnection: {
|
||||
authentication: string,
|
||||
targetSqlConnection?: {
|
||||
dataSource: string,
|
||||
username: string,
|
||||
password: string
|
||||
authentication: string,
|
||||
userName: string,
|
||||
password: string,
|
||||
encryptConnection?: boolean,
|
||||
trustServerCertificate?: boolean,
|
||||
},
|
||||
sourceSqlConnection: {
|
||||
dataSource: string,
|
||||
authentication: string,
|
||||
userName: string,
|
||||
password: string,
|
||||
encryptConnection?: boolean,
|
||||
trustServerCertificate?: boolean,
|
||||
},
|
||||
sqlDataCopyThresholds?: {
|
||||
cidxrowthreshold: number,
|
||||
cidxkbsthreshold: number,
|
||||
},
|
||||
tableList?: string[],
|
||||
scope: string,
|
||||
offlineConfiguration: OfflineConfiguration,
|
||||
offlineConfiguration?: OfflineConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,10 +645,10 @@ export interface StartDatabaseMigrationResponse {
|
||||
}
|
||||
|
||||
export interface DatabaseMigration {
|
||||
properties: DatabaseMigrationProperties;
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
properties: DatabaseMigrationProperties;
|
||||
}
|
||||
|
||||
export interface DatabaseMigrationProperties {
|
||||
@@ -525,6 +667,7 @@ export interface DatabaseMigrationProperties {
|
||||
backupConfiguration: BackupConfiguration;
|
||||
offlineConfiguration: OfflineConfiguration;
|
||||
migrationFailureError: ErrorInfo;
|
||||
tableList: string[];
|
||||
}
|
||||
|
||||
export interface MigrationStatusDetails {
|
||||
@@ -543,6 +686,7 @@ export interface MigrationStatusDetails {
|
||||
pendingLogBackupsCount: number;
|
||||
invalidFiles: string[];
|
||||
listOfCopyProgressDetails: CopyProgressDetail[];
|
||||
sqlDataCopyErrors: string[];
|
||||
}
|
||||
|
||||
export interface CopyProgressDetail {
|
||||
|
||||
282
extensions/sql-migration/src/api/sqlUtils.ts
Normal file
282
extensions/sql-migration/src/api/sqlUtils.ts
Normal file
@@ -0,0 +1,282 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { azureResource } from 'azurecore';
|
||||
import { AzureSqlDatabase, AzureSqlDatabaseServer } from './azure';
|
||||
import { generateGuid } from './utils';
|
||||
import * as utils from '../api/utils';
|
||||
|
||||
const query_database_tables_sql = `
|
||||
SELECT
|
||||
DB_NAME() as database_name,
|
||||
QUOTENAME(SCHEMA_NAME(o.schema_id)) + '.' + QUOTENAME(o.name) AS table_name,
|
||||
SUM(p.Rows) AS row_count
|
||||
FROM
|
||||
sys.objects AS o
|
||||
INNER JOIN sys.partitions AS p
|
||||
ON o.object_id = p.object_id
|
||||
WHERE
|
||||
o.type = 'U'
|
||||
AND o.is_ms_shipped = 0x0
|
||||
AND index_id < 2
|
||||
GROUP BY
|
||||
o.schema_id,
|
||||
o.name
|
||||
ORDER BY table_name;`;
|
||||
|
||||
const query_target_databases_sql = `
|
||||
SELECT
|
||||
('servername') as server_name,
|
||||
SERVERPROPERTY ('collation') as server_collation,
|
||||
db.database_id as database_id,
|
||||
db.name as database_name,
|
||||
db.collation_name as database_collation,
|
||||
CASE WHEN 'A' = 'a' THEN 0 ELSE 1 END as is_server_case_sensitive,
|
||||
db.state as database_state,
|
||||
db.is_read_only as is_read_only
|
||||
FROM sys.databases db
|
||||
WHERE
|
||||
db.name not in ('master', 'tempdb', 'model', 'msdb')
|
||||
AND is_distributor <> 1
|
||||
ORDER BY db.name;`;
|
||||
|
||||
export const excludeDatabses: string[] = [
|
||||
'master',
|
||||
'tempdb',
|
||||
'msdb',
|
||||
'model'
|
||||
];
|
||||
|
||||
export enum AuthenticationType {
|
||||
Integrated = 'Integrated',
|
||||
SqlLogin = 'SqlLogin'
|
||||
}
|
||||
|
||||
export interface TableInfo {
|
||||
databaseName: string;
|
||||
tableName: string;
|
||||
rowCount: number;
|
||||
selectedForMigration: boolean;
|
||||
}
|
||||
|
||||
export interface TargetDatabaseInfo {
|
||||
serverName: string;
|
||||
serverCollation: string;
|
||||
databaseId: string;
|
||||
databaseName: string;
|
||||
databaseCollation: string;
|
||||
isServerCaseSensitive: boolean;
|
||||
databaseState: number;
|
||||
isReadOnly: boolean;
|
||||
sourceTables: Map<string, TableInfo>;
|
||||
targetTables: Map<string, TableInfo>;
|
||||
}
|
||||
|
||||
function getSqlDbConnectionProfile(
|
||||
serverName: string,
|
||||
tenantId: string,
|
||||
databaseName: string,
|
||||
userName: string,
|
||||
password: string): azdata.IConnectionProfile {
|
||||
return {
|
||||
id: generateGuid(),
|
||||
providerName: 'MSSQL',
|
||||
connectionName: '',
|
||||
serverName: serverName,
|
||||
databaseName: databaseName,
|
||||
userName: userName,
|
||||
password: password,
|
||||
authenticationType: AuthenticationType.SqlLogin,
|
||||
savePassword: false,
|
||||
saveProfile: false,
|
||||
options: {
|
||||
conectionName: '',
|
||||
server: serverName,
|
||||
database: databaseName,
|
||||
authenticationType: AuthenticationType.SqlLogin,
|
||||
user: userName,
|
||||
password: password,
|
||||
connectionTimeout: 60,
|
||||
columnEncryptionSetting: 'Enabled',
|
||||
encrypt: true,
|
||||
trustServerCertificate: false,
|
||||
connectRetryCount: '1',
|
||||
connectRetryInterval: '10',
|
||||
applicationName: 'azdata',
|
||||
azureTenantId: tenantId,
|
||||
originalDatabase: databaseName,
|
||||
databaseDisplayName: databaseName,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function getConnectionProfile(
|
||||
serverName: string,
|
||||
azureResourceId: string,
|
||||
userName: string,
|
||||
password: string): azdata.IConnectionProfile {
|
||||
return {
|
||||
serverName: serverName,
|
||||
id: generateGuid(),
|
||||
connectionName: undefined,
|
||||
azureResourceId: azureResourceId,
|
||||
userName: userName,
|
||||
password: password,
|
||||
authenticationType: AuthenticationType.SqlLogin,
|
||||
savePassword: false,
|
||||
groupFullName: '',
|
||||
groupId: '',
|
||||
providerName: 'MSSQL',
|
||||
saveProfile: false,
|
||||
options: {
|
||||
conectionName: '',
|
||||
server: serverName,
|
||||
authenticationType: AuthenticationType.SqlLogin,
|
||||
user: userName,
|
||||
password: password,
|
||||
connectionTimeout: 60,
|
||||
columnEncryptionSetting: 'Enabled',
|
||||
encrypt: true,
|
||||
trustServerCertificate: false,
|
||||
connectRetryCount: '1',
|
||||
connectRetryInterval: '10',
|
||||
applicationName: 'azdata',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export async function collectSourceDatabaseTableInfo(sourceConnectionId: string, sourceDatabase: string): Promise<TableInfo[]> {
|
||||
const ownerUri = await azdata.connection.getUriForConnection(sourceConnectionId);
|
||||
const connectionProvider = azdata.dataprotocol.getProvider<azdata.ConnectionProvider>(
|
||||
'MSSQL',
|
||||
azdata.DataProviderType.ConnectionProvider);
|
||||
await connectionProvider.changeDatabase(ownerUri, sourceDatabase);
|
||||
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(
|
||||
'MSSQL',
|
||||
azdata.DataProviderType.QueryProvider);
|
||||
|
||||
const results = await queryProvider.runQueryAndReturn(
|
||||
ownerUri,
|
||||
query_database_tables_sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
return {
|
||||
databaseName: getSqlString(row[0]),
|
||||
tableName: getSqlString(row[1]),
|
||||
rowCount: getSqlNumber(row[2]),
|
||||
selectedForMigration: false,
|
||||
};
|
||||
}) ?? [];
|
||||
}
|
||||
|
||||
export async function collectTargetDatabaseTableInfo(
|
||||
targetServer: AzureSqlDatabaseServer,
|
||||
targetDatabaseName: string,
|
||||
tenantId: string,
|
||||
userName: string,
|
||||
password: string): Promise<TableInfo[]> {
|
||||
const connectionProfile = getSqlDbConnectionProfile(
|
||||
targetServer.properties.fullyQualifiedDomainName,
|
||||
tenantId,
|
||||
targetDatabaseName,
|
||||
userName,
|
||||
password);
|
||||
|
||||
const result = await azdata.connection.connect(connectionProfile, false, false);
|
||||
if (result.connected && result.connectionId) {
|
||||
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(
|
||||
'MSSQL',
|
||||
azdata.DataProviderType.QueryProvider);
|
||||
|
||||
const ownerUri = await azdata.connection.getUriForConnection(result.connectionId);
|
||||
const results = await queryProvider.runQueryAndReturn(
|
||||
ownerUri,
|
||||
query_database_tables_sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
return {
|
||||
databaseName: getSqlString(row[0]),
|
||||
tableName: getSqlString(row[1]),
|
||||
rowCount: getSqlNumber(row[2]),
|
||||
selectedForMigration: false,
|
||||
};
|
||||
}) ?? [];
|
||||
}
|
||||
|
||||
throw new Error(result.errorMessage);
|
||||
}
|
||||
|
||||
export async function collectTargetDatabaseInfo(
|
||||
targetServer: AzureSqlDatabaseServer,
|
||||
userName: string,
|
||||
password: string): Promise<TargetDatabaseInfo[]> {
|
||||
|
||||
const connectionProfile = getConnectionProfile(
|
||||
targetServer.properties.fullyQualifiedDomainName,
|
||||
targetServer.id,
|
||||
userName,
|
||||
password);
|
||||
|
||||
const result = await azdata.connection.connect(connectionProfile, false, false);
|
||||
if (result.connected && result.connectionId) {
|
||||
const queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(
|
||||
'MSSQL',
|
||||
azdata.DataProviderType.QueryProvider);
|
||||
|
||||
const ownerUri = await azdata.connection.getUriForConnection(result.connectionId);
|
||||
const results = await queryProvider.runQueryAndReturn(
|
||||
ownerUri,
|
||||
query_target_databases_sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
return {
|
||||
serverName: getSqlString(row[0]),
|
||||
serverCollation: getSqlString(row[1]),
|
||||
databaseId: getSqlString(row[2]),
|
||||
databaseName: getSqlString(row[3]),
|
||||
databaseCollation: getSqlString(row[4]),
|
||||
isServerCaseSensitive: getSqlBoolean(row[5]),
|
||||
databaseState: getSqlNumber(row[6]),
|
||||
isReadOnly: getSqlBoolean(row[7]),
|
||||
sourceTables: new Map(),
|
||||
targetTables: new Map(),
|
||||
};
|
||||
}) ?? [];
|
||||
}
|
||||
|
||||
throw new Error(result.errorMessage);
|
||||
}
|
||||
|
||||
export async function collectAzureTargetDatabases(
|
||||
account: azdata.Account,
|
||||
subscription: azureResource.AzureResourceSubscription,
|
||||
resourceGroup: string,
|
||||
targetServerName: string,
|
||||
): Promise<AzureSqlDatabase[]> {
|
||||
const databaseList: AzureSqlDatabase[] = [];
|
||||
if (resourceGroup && targetServerName) {
|
||||
databaseList.push(...
|
||||
await utils.getAzureSqlDatabases(
|
||||
account,
|
||||
subscription,
|
||||
resourceGroup,
|
||||
targetServerName));
|
||||
}
|
||||
return databaseList.filter(
|
||||
database => !excludeDatabses.includes(database.name)) ?? [];
|
||||
}
|
||||
|
||||
export function getSqlString(value: azdata.DbCellValue): string {
|
||||
return value.isNull ? '' : value.displayValue;
|
||||
}
|
||||
|
||||
export function getSqlNumber(value: azdata.DbCellValue): number {
|
||||
return value.isNull ? 0 : parseInt(value.displayValue);
|
||||
}
|
||||
|
||||
export function getSqlBoolean(value: azdata.DbCellValue): boolean {
|
||||
return value.isNull ? false : value.displayValue === '1';
|
||||
}
|
||||
@@ -3,10 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { window, Account, accounts, CategoryValue, DropDownComponent, IconPath } from 'azdata';
|
||||
import { window, Account, accounts, CategoryValue, DropDownComponent, IconPath, DisplayType, Component } from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import { IconPathHelper } from '../constants/iconPathHelper';
|
||||
import { MigrationStatus, ProvisioningState } from '../models/migrationLocalStorage';
|
||||
import * as crypto from 'crypto';
|
||||
import * as azure from './azure';
|
||||
import { azureResource, Tenant } from 'azurecore';
|
||||
@@ -15,8 +14,26 @@ import { logError, TelemetryViews } from '../telemtery';
|
||||
import { AdsMigrationStatus } from '../dashboard/tabBase';
|
||||
import { getMigrationMode, getMigrationStatus, getMigrationTargetType, PipelineStatusCodes } from '../constants/helper';
|
||||
|
||||
export type TargetServerType = azure.SqlVMServer | azureResource.AzureSqlManagedInstance | azure.AzureSqlDatabaseServer;
|
||||
|
||||
export const SqlMigrationExtensionId = 'microsoft.sql-migration';
|
||||
export const DefaultSettingValue = '---';
|
||||
|
||||
export const MenuCommands = {
|
||||
Cutover: 'sqlmigration.cutover',
|
||||
ViewDatabase: 'sqlmigration.view.database',
|
||||
ViewTarget: 'sqlmigration.view.target',
|
||||
ViewService: 'sqlmigration.view.service',
|
||||
CopyMigration: 'sqlmigration.copy.migration',
|
||||
CancelMigration: 'sqlmigration.cancel.migration',
|
||||
RetryMigration: 'sqlmigration.retry.migration',
|
||||
StartMigration: 'sqlmigration.start',
|
||||
IssueReporter: 'workbench.action.openIssueReporter',
|
||||
OpenNotebooks: 'sqlmigration.openNotebooks',
|
||||
NewSupportRequest: 'sqlmigration.newsupportrequest',
|
||||
SendFeedback: 'sqlmigration.sendfeedback',
|
||||
};
|
||||
|
||||
export function deepClone<T>(obj: T): T {
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
return obj;
|
||||
@@ -145,19 +162,19 @@ export function filterMigrations(databaseMigrations: azure.DatabaseMigration[],
|
||||
return filteredMigration.filter(
|
||||
value => {
|
||||
const status = getMigrationStatus(value);
|
||||
return status === MigrationStatus.InProgress
|
||||
|| status === MigrationStatus.Retriable
|
||||
|| status === MigrationStatus.Creating;
|
||||
return status === constants.MigrationStatus.InProgress
|
||||
|| status === constants.MigrationStatus.Retriable
|
||||
|| status === constants.MigrationStatus.Creating;
|
||||
});
|
||||
case AdsMigrationStatus.SUCCEEDED:
|
||||
return filteredMigration.filter(
|
||||
value => getMigrationStatus(value) === MigrationStatus.Succeeded);
|
||||
value => getMigrationStatus(value) === constants.MigrationStatus.Succeeded);
|
||||
case AdsMigrationStatus.FAILED:
|
||||
return filteredMigration.filter(
|
||||
value => getMigrationStatus(value) === MigrationStatus.Failed);
|
||||
value => getMigrationStatus(value) === constants.MigrationStatus.Failed);
|
||||
case AdsMigrationStatus.COMPLETING:
|
||||
return filteredMigration.filter(
|
||||
value => getMigrationStatus(value) === MigrationStatus.Completing);
|
||||
value => getMigrationStatus(value) === constants.MigrationStatus.Completing);
|
||||
}
|
||||
return filteredMigration;
|
||||
}
|
||||
@@ -192,6 +209,8 @@ export function selectDefaultDropdownValue(dropDown: DropDownComponent, value?:
|
||||
selectedIndex = -1;
|
||||
}
|
||||
selectDropDownIndex(dropDown, selectedIndex > -1 ? selectedIndex : 0);
|
||||
} else {
|
||||
dropDown.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,19 +270,22 @@ export function getSessionIdHeader(sessionId: string): { [key: string]: string }
|
||||
|
||||
export function getMigrationStatusWithErrors(migration: azure.DatabaseMigration): string {
|
||||
const properties = migration.properties;
|
||||
const migrationStatus = properties.migrationStatus ?? properties.provisioningState;
|
||||
let warningCount = 0;
|
||||
const migrationStatus = getMigrationStatus(migration) ?? '';
|
||||
|
||||
if (properties.migrationFailureError?.message) {
|
||||
warningCount++;
|
||||
}
|
||||
if (properties.migrationStatusDetails?.fileUploadBlockingErrors) {
|
||||
const blockingErrors = properties.migrationStatusDetails?.fileUploadBlockingErrors.length ?? 0;
|
||||
warningCount += blockingErrors;
|
||||
}
|
||||
if (properties.migrationStatusDetails?.restoreBlockingReason) {
|
||||
warningCount++;
|
||||
}
|
||||
// provisioning error
|
||||
let warningCount = properties.provisioningError?.length > 0 ? 1 : 0;
|
||||
|
||||
// migration failure error
|
||||
warningCount += properties.migrationFailureError?.message?.length > 0 ? 1 : 0;
|
||||
|
||||
// file upload blocking errors
|
||||
warningCount += properties.migrationStatusDetails?.fileUploadBlockingErrors?.length ?? 0;
|
||||
|
||||
// restore blocking reason
|
||||
warningCount += properties.migrationStatusDetails?.restoreBlockingReason ? 1 : 0;
|
||||
|
||||
// sql data copy errors
|
||||
warningCount += properties.migrationStatusDetails?.sqlDataCopyErrors?.length ?? 0;
|
||||
|
||||
return constants.STATUS_VALUE(migrationStatus, warningCount)
|
||||
+ (constants.STATUS_WARNING_COUNT(migrationStatus, warningCount) ?? '');
|
||||
@@ -302,20 +324,20 @@ export function getPipelineStatusImage(status: string | undefined): IconPath {
|
||||
export function getMigrationStatusImage(migration: azure.DatabaseMigration): IconPath {
|
||||
const status = getMigrationStatus(migration);
|
||||
switch (status) {
|
||||
case MigrationStatus.InProgress:
|
||||
case constants.MigrationStatus.InProgress:
|
||||
return IconPathHelper.inProgressMigration;
|
||||
case MigrationStatus.Succeeded:
|
||||
case constants.MigrationStatus.Succeeded:
|
||||
return IconPathHelper.completedMigration;
|
||||
case MigrationStatus.Creating:
|
||||
case constants.MigrationStatus.Creating:
|
||||
return IconPathHelper.notStartedMigration;
|
||||
case MigrationStatus.Completing:
|
||||
case constants.MigrationStatus.Completing:
|
||||
return IconPathHelper.completingCutover;
|
||||
case MigrationStatus.Retriable:
|
||||
case constants.MigrationStatus.Retriable:
|
||||
return IconPathHelper.retry;
|
||||
case MigrationStatus.Canceling:
|
||||
case MigrationStatus.Canceled:
|
||||
case constants.MigrationStatus.Canceling:
|
||||
case constants.MigrationStatus.Canceled:
|
||||
return IconPathHelper.cancel;
|
||||
case MigrationStatus.Failed:
|
||||
case constants.MigrationStatus.Failed:
|
||||
default:
|
||||
return IconPathHelper.error;
|
||||
}
|
||||
@@ -379,34 +401,7 @@ export async function getAzureAccountsDropdownValues(accounts: Account[]): Promi
|
||||
}
|
||||
|
||||
export function getAzureTenants(account?: Account): Tenant[] {
|
||||
let tenants: Tenant[] = [];
|
||||
try {
|
||||
if (account) {
|
||||
tenants = account.properties.tenants;
|
||||
}
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.Utils, 'utils.getAzureTenants', e);
|
||||
}
|
||||
return tenants;
|
||||
}
|
||||
|
||||
export async function getAzureTenantsDropdownValues(tenants: Tenant[]): Promise<CategoryValue[]> {
|
||||
let tenantsValues: CategoryValue[] = [];
|
||||
tenants.forEach((tenant) => {
|
||||
tenantsValues.push({
|
||||
name: tenant.id,
|
||||
displayName: tenant.displayName
|
||||
});
|
||||
});
|
||||
if (tenantsValues.length === 0) {
|
||||
tenantsValues = [
|
||||
{
|
||||
displayName: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR,
|
||||
name: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
return tenantsValues;
|
||||
return account?.properties.tenants || [];
|
||||
}
|
||||
|
||||
export async function getAzureSubscriptions(account?: Account): Promise<azureResource.AzureResourceSubscription[]> {
|
||||
@@ -441,172 +436,58 @@ export async function getAzureSubscriptionsDropdownValues(subscriptions: azureRe
|
||||
return subscriptionsValues;
|
||||
}
|
||||
|
||||
export async function getSqlManagedInstanceLocations(account?: Account, subscription?: azureResource.AzureResourceSubscription, managedInstances?: azureResource.AzureSqlManagedInstance[]): Promise<azureResource.AzureLocation[]> {
|
||||
let locations: azureResource.AzureLocation[] = [];
|
||||
export async function getResourceLocations(
|
||||
account?: Account,
|
||||
subscription?: azureResource.AzureResourceSubscription,
|
||||
resources?: { location: string }[]): Promise<azureResource.AzureLocation[]> {
|
||||
|
||||
try {
|
||||
if (account && subscription && managedInstances) {
|
||||
locations = await azure.getLocations(account, subscription);
|
||||
locations = locations.filter((loc, i) => managedInstances.some(mi => mi.location.toLowerCase() === loc.name.toLowerCase()));
|
||||
if (account && subscription && resources) {
|
||||
const locations = await azure.getLocations(account, subscription);
|
||||
return locations
|
||||
.filter((loc, i) => resources.some(resource => resource.location.toLowerCase() === loc.name.toLowerCase()))
|
||||
.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
||||
}
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.Utils, 'utils.getSqlManagedInstanceLocations', e);
|
||||
logError(TelemetryViews.Utils, 'utils.getResourceLocations', e);
|
||||
}
|
||||
locations.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
||||
return locations;
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function getSqlVirtualMachineLocations(account?: Account, subscription?: azureResource.AzureResourceSubscription, virtualMachines?: azure.SqlVMServer[]): Promise<azureResource.AzureLocation[]> {
|
||||
let locations: azureResource.AzureLocation[] = [];
|
||||
try {
|
||||
if (account && subscription && virtualMachines) {
|
||||
locations = await azure.getLocations(account, subscription);
|
||||
locations = locations.filter((loc, i) => virtualMachines.some(vm => vm.location.toLowerCase() === loc.name.toLowerCase()));
|
||||
}
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.Utils, 'utils.getSqlVirtualMachineLocations', e);
|
||||
}
|
||||
locations.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
||||
return locations;
|
||||
}
|
||||
export function getServiceResourceGroupsByLocation(
|
||||
resources: { location: string, id: string, tenantId?: string }[],
|
||||
location: azureResource.AzureLocation): azureResource.AzureResourceResourceGroup[] {
|
||||
|
||||
export async function getSqlMigrationServiceLocations(account?: Account, subscription?: azureResource.AzureResourceSubscription, migrationServices?: azure.SqlMigrationService[]): Promise<azureResource.AzureLocation[]> {
|
||||
let locations: azureResource.AzureLocation[] = [];
|
||||
try {
|
||||
if (account && subscription && migrationServices) {
|
||||
locations = await azure.getLocations(account, subscription);
|
||||
locations = locations.filter((loc, i) => migrationServices.some(dms => dms.location.toLowerCase() === loc.name.toLowerCase()));
|
||||
}
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.Utils, 'utils.getSqlMigrationServiceLocations', e);
|
||||
}
|
||||
locations.sort((a, b) => a.displayName.localeCompare(b.displayName));
|
||||
return locations;
|
||||
}
|
||||
|
||||
export async function getAzureLocationsDropdownValues(locations: azureResource.AzureLocation[]): Promise<CategoryValue[]> {
|
||||
let locationValues: CategoryValue[] = [];
|
||||
locations.forEach((loc) => {
|
||||
locationValues.push({
|
||||
name: loc.name,
|
||||
displayName: loc.displayName
|
||||
});
|
||||
});
|
||||
if (locationValues.length === 0) {
|
||||
locationValues = [
|
||||
{
|
||||
displayName: constants.NO_LOCATION_FOUND,
|
||||
name: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
return locationValues;
|
||||
}
|
||||
|
||||
export async function getSqlManagedInstanceResourceGroups(managedInstances?: azureResource.AzureSqlManagedInstance[], location?: azureResource.AzureLocation): Promise<azureResource.AzureResourceResourceGroup[]> {
|
||||
let resourceGroups: azureResource.AzureResourceResourceGroup[] = [];
|
||||
try {
|
||||
if (managedInstances && location) {
|
||||
resourceGroups = managedInstances
|
||||
.filter((mi) => mi.location.toLowerCase() === location.name.toLowerCase())
|
||||
.map((mi) => {
|
||||
return <azureResource.AzureResourceResourceGroup>{
|
||||
id: azure.getFullResourceGroupFromId(mi.id),
|
||||
name: azure.getResourceGroupFromId(mi.id),
|
||||
subscription: {
|
||||
id: mi.subscriptionId
|
||||
},
|
||||
tenant: mi.tenantId
|
||||
};
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.Utils, 'utils.getSqlManagedInstanceResourceGroups', e);
|
||||
if (resources && location) {
|
||||
const locationName = location.name.toLowerCase();
|
||||
resourceGroups = resources
|
||||
.filter(resource => resource.location.toLowerCase() === locationName)
|
||||
.map(resource => {
|
||||
return <azureResource.AzureResourceResourceGroup>{
|
||||
id: azure.getFullResourceGroupFromId(resource.id),
|
||||
name: azure.getResourceGroupFromId(resource.id),
|
||||
subscription: { id: getSubscriptionIdFromResourceId(resource.id) },
|
||||
tenant: resource.tenantId
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
resourceGroups = resourceGroups.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
|
||||
resourceGroups.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return resourceGroups;
|
||||
return resourceGroups
|
||||
.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
|
||||
export async function getSqlVirtualMachineResourceGroups(virtualMachines?: azure.SqlVMServer[], location?: azureResource.AzureLocation): Promise<azureResource.AzureResourceResourceGroup[]> {
|
||||
let resourceGroups: azureResource.AzureResourceResourceGroup[] = [];
|
||||
try {
|
||||
if (virtualMachines && location) {
|
||||
resourceGroups = virtualMachines
|
||||
.filter((vm) => vm.location.toLowerCase() === location.name.toLowerCase())
|
||||
.map((vm) => {
|
||||
return <azureResource.AzureResourceResourceGroup>{
|
||||
id: azure.getFullResourceGroupFromId(vm.id),
|
||||
name: azure.getResourceGroupFromId(vm.id),
|
||||
subscription: {
|
||||
id: vm.subscriptionId
|
||||
},
|
||||
tenant: vm.tenantId
|
||||
};
|
||||
});
|
||||
export function getSubscriptionIdFromResourceId(resourceId: string): string | undefined {
|
||||
let parts = resourceId?.split('/subscriptions/');
|
||||
if (parts?.length > 1) {
|
||||
parts = parts[1]?.split('/resourcegroups/');
|
||||
if (parts?.length > 0) {
|
||||
return parts[0];
|
||||
}
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.Utils, 'utils.getSqlVirtualMachineResourceGroups', e);
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
resourceGroups = resourceGroups.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
|
||||
resourceGroups.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return resourceGroups;
|
||||
}
|
||||
|
||||
export async function getStorageAccountResourceGroups(storageAccounts?: azure.StorageAccount[], location?: azureResource.AzureLocation): Promise<azureResource.AzureResourceResourceGroup[]> {
|
||||
let resourceGroups: azureResource.AzureResourceResourceGroup[] = [];
|
||||
try {
|
||||
if (storageAccounts && location) {
|
||||
resourceGroups = storageAccounts
|
||||
.filter((sa) => sa.location.toLowerCase() === location.name.toLowerCase())
|
||||
.map((sa) => {
|
||||
return <azureResource.AzureResourceResourceGroup>{
|
||||
id: azure.getFullResourceGroupFromId(sa.id),
|
||||
name: azure.getResourceGroupFromId(sa.id),
|
||||
subscription: {
|
||||
id: sa.subscriptionId
|
||||
},
|
||||
tenant: sa.tenantId
|
||||
};
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.Utils, 'utils.getStorageAccountResourceGroups', e);
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
resourceGroups = resourceGroups.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
|
||||
resourceGroups.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return resourceGroups;
|
||||
}
|
||||
|
||||
export async function getSqlMigrationServiceResourceGroups(migrationServices?: azure.SqlMigrationService[], location?: azureResource.AzureLocation): Promise<azureResource.AzureResourceResourceGroup[]> {
|
||||
let resourceGroups: azureResource.AzureResourceResourceGroup[] = [];
|
||||
try {
|
||||
if (migrationServices && location) {
|
||||
resourceGroups = migrationServices
|
||||
.filter((dms) => dms.properties.provisioningState === ProvisioningState.Succeeded && dms.location.toLowerCase() === location.name.toLowerCase())
|
||||
.map((dms) => {
|
||||
return <azureResource.AzureResourceResourceGroup>{
|
||||
id: azure.getFullResourceGroupFromId(dms.id),
|
||||
name: azure.getResourceGroupFromId(dms.id),
|
||||
subscription: {
|
||||
id: dms.properties.subscriptionId
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.Utils, 'utils.getSqlMigrationServiceResourceGroups', e);
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
resourceGroups = resourceGroups.filter((v, i, a) => a.findIndex(v2 => (v2.id === v.id)) === i);
|
||||
resourceGroups.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return resourceGroups;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export async function getAllResourceGroups(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azureResource.AzureResourceResourceGroup[]> {
|
||||
@@ -622,25 +503,6 @@ export async function getAllResourceGroups(account?: Account, subscription?: azu
|
||||
return resourceGroups;
|
||||
}
|
||||
|
||||
export async function getAzureResourceGroupsDropdownValues(resourceGroups: azureResource.AzureResourceResourceGroup[]): Promise<CategoryValue[]> {
|
||||
let resourceGroupValues: CategoryValue[] = [];
|
||||
resourceGroups.forEach((rg) => {
|
||||
resourceGroupValues.push({
|
||||
name: rg.id,
|
||||
displayName: rg.name
|
||||
});
|
||||
});
|
||||
if (resourceGroupValues.length === 0) {
|
||||
resourceGroupValues = [
|
||||
{
|
||||
displayName: constants.RESOURCE_GROUP_NOT_FOUND,
|
||||
name: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
return resourceGroupValues;
|
||||
}
|
||||
|
||||
export async function getManagedInstances(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azureResource.AzureSqlManagedInstance[]> {
|
||||
let managedInstances: azureResource.AzureSqlManagedInstance[] = [];
|
||||
try {
|
||||
@@ -687,6 +549,31 @@ export async function getManagedInstancesDropdownValues(managedInstances: azureR
|
||||
return managedInstancesValues;
|
||||
}
|
||||
|
||||
export async function getAzureSqlDatabaseServers(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azure.AzureSqlDatabaseServer[]> {
|
||||
let sqlDatabaseServers: azure.AzureSqlDatabaseServer[] = [];
|
||||
try {
|
||||
if (account && subscription) {
|
||||
sqlDatabaseServers = await azure.getAvailableSqlDatabaseServers(account, subscription);
|
||||
}
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.Utils, 'utils.getAzureSqlDatabaseServers', e);
|
||||
}
|
||||
sqlDatabaseServers.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return sqlDatabaseServers;
|
||||
}
|
||||
|
||||
export async function getAzureSqlDatabases(account?: Account, subscription?: azureResource.AzureResourceSubscription, resourceGroupName?: string, serverName?: string): Promise<azure.AzureSqlDatabase[]> {
|
||||
if (account && subscription && resourceGroupName && serverName) {
|
||||
try {
|
||||
const databases = await azure.getAvailableSqlDatabases(account, subscription, resourceGroupName, serverName);
|
||||
return databases.sort((a, b) => a.name.localeCompare(b.name));
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.Utils, 'utils.getAzureSqlDatabases', e);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function getVirtualMachines(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azure.SqlVMServer[]> {
|
||||
let virtualMachines: azure.SqlVMServer[] = [];
|
||||
try {
|
||||
@@ -705,30 +592,6 @@ export async function getVirtualMachines(account?: Account, subscription?: azure
|
||||
return virtualMachines;
|
||||
}
|
||||
|
||||
export async function getVirtualMachinesDropdownValues(virtualMachines: azure.SqlVMServer[], location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<CategoryValue[]> {
|
||||
let virtualMachineValues: CategoryValue[] = [];
|
||||
if (location && resourceGroup) {
|
||||
virtualMachines.forEach((virtualMachine) => {
|
||||
if (virtualMachine.location.toLowerCase() === location.name.toLowerCase() && azure.getResourceGroupFromId(virtualMachine.id).toLowerCase() === resourceGroup.name.toLowerCase()) {
|
||||
virtualMachineValues.push({
|
||||
name: virtualMachine.id,
|
||||
displayName: virtualMachine.name
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (virtualMachineValues.length === 0) {
|
||||
virtualMachineValues = [
|
||||
{
|
||||
displayName: constants.NO_VIRTUAL_MACHINE_FOUND,
|
||||
name: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
return virtualMachineValues;
|
||||
}
|
||||
|
||||
export async function getStorageAccounts(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azure.StorageAccount[]> {
|
||||
let storageAccounts: azure.StorageAccount[] = [];
|
||||
try {
|
||||
@@ -742,65 +605,18 @@ export async function getStorageAccounts(account?: Account, subscription?: azure
|
||||
return storageAccounts;
|
||||
}
|
||||
|
||||
export async function getStorageAccountsDropdownValues(storageAccounts: azure.StorageAccount[], location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<CategoryValue[]> {
|
||||
let storageAccountValues: CategoryValue[] = [];
|
||||
storageAccounts.forEach((storageAccount) => {
|
||||
if (storageAccount.location.toLowerCase() === location.name.toLowerCase() && storageAccount.resourceGroup?.toLowerCase() === resourceGroup.name.toLowerCase()) {
|
||||
storageAccountValues.push({
|
||||
name: storageAccount.id,
|
||||
displayName: storageAccount.name
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (storageAccountValues.length === 0) {
|
||||
storageAccountValues = [
|
||||
{
|
||||
displayName: constants.NO_STORAGE_ACCOUNT_FOUND,
|
||||
name: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
return storageAccountValues;
|
||||
}
|
||||
|
||||
export async function getAzureSqlMigrationServices(account?: Account, subscription?: azureResource.AzureResourceSubscription): Promise<azure.SqlMigrationService[]> {
|
||||
let sqlMigrationServices: azure.SqlMigrationService[] = [];
|
||||
try {
|
||||
if (account && subscription) {
|
||||
sqlMigrationServices = (await azure.getSqlMigrationServices(account, subscription)).filter(dms => {
|
||||
return dms.properties.provisioningState === ProvisioningState.Succeeded;
|
||||
});
|
||||
const services = await azure.getSqlMigrationServices(account, subscription);
|
||||
return services
|
||||
.filter(dms => dms.properties.provisioningState === constants.ProvisioningState.Succeeded)
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
}
|
||||
} catch (e) {
|
||||
logError(TelemetryViews.Utils, 'utils.getAzureSqlMigrationServices', e);
|
||||
}
|
||||
sqlMigrationServices.sort((a, b) => a.name.localeCompare(b.name));
|
||||
return sqlMigrationServices;
|
||||
}
|
||||
|
||||
export async function getAzureSqlMigrationServicesDropdownValues(sqlMigrationServices: azure.SqlMigrationService[], location: azureResource.AzureLocation, resourceGroup: azureResource.AzureResourceResourceGroup): Promise<CategoryValue[]> {
|
||||
let SqlMigrationServicesValues: CategoryValue[] = [];
|
||||
if (location && resourceGroup) {
|
||||
sqlMigrationServices.forEach((sqlMigrationService) => {
|
||||
if (sqlMigrationService.location.toLowerCase() === location.name.toLowerCase() && sqlMigrationService.properties.resourceGroup.toLowerCase() === resourceGroup.name.toLowerCase()) {
|
||||
SqlMigrationServicesValues.push({
|
||||
name: sqlMigrationService.id,
|
||||
displayName: sqlMigrationService.name
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (SqlMigrationServicesValues.length === 0) {
|
||||
SqlMigrationServicesValues = [
|
||||
{
|
||||
displayName: constants.SQL_MIGRATION_SERVICE_NOT_FOUND_ERROR,
|
||||
name: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
return SqlMigrationServicesValues;
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function getBlobContainer(account?: Account, subscription?: azureResource.AzureResourceSubscription, storageAccount?: azure.StorageAccount): Promise<azureResource.BlobContainer[]> {
|
||||
@@ -816,25 +632,6 @@ export async function getBlobContainer(account?: Account, subscription?: azureRe
|
||||
return blobContainers;
|
||||
}
|
||||
|
||||
export async function getBlobContainersValues(blobContainers: azureResource.BlobContainer[]): Promise<CategoryValue[]> {
|
||||
let blobContainersValues: CategoryValue[] = [];
|
||||
blobContainers.forEach((blobContainer) => {
|
||||
blobContainersValues.push({
|
||||
name: blobContainer.id,
|
||||
displayName: blobContainer.name
|
||||
});
|
||||
});
|
||||
if (blobContainersValues.length === 0) {
|
||||
blobContainersValues = [
|
||||
{
|
||||
displayName: constants.NO_BLOBCONTAINERS_FOUND,
|
||||
name: ''
|
||||
}
|
||||
];
|
||||
}
|
||||
return blobContainersValues;
|
||||
}
|
||||
|
||||
export async function getBlobLastBackupFileNames(account?: Account, subscription?: azureResource.AzureResourceSubscription, storageAccount?: azure.StorageAccount, blobContainer?: azureResource.BlobContainer): Promise<azureResource.Blob[]> {
|
||||
let lastFileNames: azureResource.Blob[] = [];
|
||||
try {
|
||||
@@ -848,39 +645,91 @@ export async function getBlobLastBackupFileNames(account?: Account, subscription
|
||||
return lastFileNames;
|
||||
}
|
||||
|
||||
export async function getBlobLastBackupFileNamesValues(lastFileNames: azureResource.Blob[]): Promise<CategoryValue[]> {
|
||||
let lastFileNamesValues: CategoryValue[] = [];
|
||||
lastFileNames.forEach((lastFileName) => {
|
||||
lastFileNamesValues.push({
|
||||
name: lastFileName.name,
|
||||
displayName: lastFileName.name
|
||||
});
|
||||
});
|
||||
if (lastFileNamesValues.length === 0) {
|
||||
lastFileNamesValues = [
|
||||
{
|
||||
displayName: constants.NO_BLOBFILES_FOUND,
|
||||
name: ''
|
||||
}
|
||||
];
|
||||
export function getAzureResourceDropdownValues(
|
||||
azureResources: { location: string, id: string, name: string }[],
|
||||
location: azureResource.AzureLocation | undefined,
|
||||
resourceGroup: string | undefined,
|
||||
resourceNotFoundMessage: string): CategoryValue[] {
|
||||
|
||||
if (location?.name && resourceGroup && azureResources?.length > 0) {
|
||||
const locationName = location.name.toLowerCase();
|
||||
const resourceGroupName = resourceGroup.toLowerCase();
|
||||
|
||||
return azureResources
|
||||
.filter(resource =>
|
||||
resource.location?.toLowerCase() === locationName &&
|
||||
azure.getResourceGroupFromId(resource.id)?.toLowerCase() === resourceGroupName)
|
||||
.map(resource => {
|
||||
return { name: resource.id, displayName: resource.name };
|
||||
});
|
||||
}
|
||||
return lastFileNamesValues;
|
||||
|
||||
return [{ name: '', displayName: resourceNotFoundMessage }];
|
||||
}
|
||||
|
||||
export function getResourceDropdownValues(resources: { id: string, name: string }[], resourceNotFoundMessage: string): CategoryValue[] {
|
||||
return resources?.map(resource => { return { name: resource.id, displayName: resource.name }; })
|
||||
|| [{ name: '', displayName: resourceNotFoundMessage }];
|
||||
}
|
||||
|
||||
export async function getAzureTenantsDropdownValues(tenants: Tenant[]): Promise<CategoryValue[]> {
|
||||
return tenants?.map(tenant => { return { name: tenant.id, displayName: tenant.displayName }; })
|
||||
|| [{ name: '', displayName: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR }];
|
||||
}
|
||||
|
||||
export async function getAzureLocationsDropdownValues(locations: azureResource.AzureLocation[]): Promise<CategoryValue[]> {
|
||||
return locations?.map(location => { return { name: location.name, displayName: location.displayName }; })
|
||||
|| [{ name: '', displayName: constants.NO_LOCATION_FOUND }];
|
||||
}
|
||||
|
||||
export async function getBlobLastBackupFileNamesValues(blobs: azureResource.Blob[]): Promise<CategoryValue[]> {
|
||||
return blobs?.map(blob => { return { name: blob.name, displayName: blob.name }; })
|
||||
|| [{ name: '', displayName: constants.NO_BLOBFILES_FOUND }];
|
||||
}
|
||||
|
||||
export async function updateControlDisplay(control: Component, visible: boolean, displayStyle: DisplayType = 'inline'): Promise<void> {
|
||||
const display = visible ? displayStyle : 'none';
|
||||
control.display = display;
|
||||
await control.updateCssStyles({ 'display': display });
|
||||
await control.updateProperties({ 'display': display });
|
||||
}
|
||||
|
||||
export function generateGuid(): string {
|
||||
const hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
||||
let oct: string = '';
|
||||
let tmp: number;
|
||||
/* tslint:disable:no-bitwise */
|
||||
for (let a: number = 0; a < 4; a++) {
|
||||
tmp = (4294967296 * Math.random()) | 0;
|
||||
oct += hexValues[tmp & 0xF] +
|
||||
hexValues[tmp >> 4 & 0xF] +
|
||||
hexValues[tmp >> 8 & 0xF] +
|
||||
hexValues[tmp >> 12 & 0xF] +
|
||||
hexValues[tmp >> 16 & 0xF] +
|
||||
hexValues[tmp >> 20 & 0xF] +
|
||||
hexValues[tmp >> 24 & 0xF] +
|
||||
hexValues[tmp >> 28 & 0xF];
|
||||
}
|
||||
|
||||
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
|
||||
const clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
||||
return `${oct.substr(0, 8)}-${oct.substr(9, 4)}-4${oct.substr(13, 3)}-${clockSequenceHi}${oct.substr(16, 3)}-${oct.substr(19, 12)}`;
|
||||
/* tslint:enable:no-bitwise */
|
||||
}
|
||||
|
||||
export async function promptUserForFolder(): Promise<string> {
|
||||
let path = '';
|
||||
|
||||
let options: vscode.OpenDialogOptions = {
|
||||
const options: vscode.OpenDialogOptions = {
|
||||
defaultUri: vscode.Uri.file(getUserHome()!),
|
||||
canSelectFiles: false,
|
||||
canSelectFolders: true,
|
||||
canSelectMany: false,
|
||||
};
|
||||
|
||||
let fileUris = await vscode.window.showOpenDialog(options);
|
||||
if (fileUris && fileUris?.length > 0 && fileUris[0]) {
|
||||
path = fileUris[0].fsPath;
|
||||
const fileUris = await vscode.window.showOpenDialog(options);
|
||||
if (fileUris && fileUris.length > 0 && fileUris[0]) {
|
||||
return fileUris[0].fsPath;
|
||||
}
|
||||
|
||||
return path;
|
||||
return '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user