[SQL Migration] Allow folders inside blob containers (#21952)

* WIP

* WIP

* WIP - add new property to blob

* Add error messages

* Fix undefined for offline scenario

* Add support for offline scenario

* Clean up

* vbump extension

* remove >1 level deep folders

* fix [object] object issue

* Remove unnecessary asyncs

* don't allow >1 level deep for offline scenario lastBackupFile
This commit is contained in:
Raymond Truong
2023-02-23 10:40:56 -08:00
committed by GitHub
parent fd282cd20b
commit 85056fb1b7
8 changed files with 214 additions and 44 deletions

View File

@@ -705,6 +705,15 @@ export async function getAzureSqlMigrationServices(account?: Account, subscripti
return [];
}
export interface Blob {
resourceGroup: azureResource.AzureResourceResourceGroup;
storageAccount: azureResource.AzureGraphResource;
blobContainer: azureResource.BlobContainer;
storageKey: string;
lastBackupFile?: string;
folderName?: string;
}
export async function getBlobContainer(account?: Account, subscription?: azureResource.AzureResourceSubscription, storageAccount?: azure.StorageAccount): Promise<azureResource.BlobContainer[]> {
let blobContainers: azureResource.BlobContainer[] = [];
try {
@@ -722,7 +731,14 @@ export async function getBlobLastBackupFileNames(account?: Account, subscription
let lastFileNames: azureResource.Blob[] = [];
try {
if (account && subscription && storageAccount && blobContainer) {
lastFileNames = await azure.getBlobs(account, subscription, storageAccount, blobContainer.name);
const blobs = await azure.getBlobs(account, subscription, storageAccount, blobContainer.name);
blobs.forEach(blob => {
// only show at most one folder deep
if ((blob.name.split('/').length === 1 || blob.name.split('/').length === 2) && !lastFileNames.includes(blob)) {
lastFileNames.push(blob);
}
});
}
} catch (e) {
logError(TelemetryViews.Utils, 'utils.getBlobLastBackupFileNames', e);
@@ -731,6 +747,64 @@ export async function getBlobLastBackupFileNames(account?: Account, subscription
return lastFileNames;
}
export async function getBlobFolders(account?: Account, subscription?: azureResource.AzureResourceSubscription, storageAccount?: azure.StorageAccount, blobContainer?: azureResource.BlobContainer): Promise<string[]> {
let folders: string[] = [];
try {
if (account && subscription && storageAccount && blobContainer) {
const blobs = await azure.getBlobs(account, subscription, storageAccount, blobContainer.name);
blobs.forEach(blob => {
let folder: string = '';
if (blob.name.split('/').length === 1) {
folder = '/'; // no folder (root)
} else if (blob.name.split('/').length === 2) {
folder = blob.name.split('/')[0]; // one folder deep
}
if (folder && !folders.includes(folder)) {
folders.push(folder);
}
});
}
} catch (e) {
logError(TelemetryViews.Utils, 'utils.getBlobLastBackupFolders', e);
}
folders.sort();
return folders;
}
export function getBlobContainerNameWithFolder(blob: Blob, isOfflineMigration: boolean): string {
const blobContainerName = blob.blobContainer.name;
if (isOfflineMigration) {
const lastBackupFile = blob.lastBackupFile;
if (!lastBackupFile || lastBackupFile.split('/').length !== 2) {
return blobContainerName;
}
// for offline scenario, take the folder name out of the blob name and add it to the container name instead
return blobContainerName + '/' + lastBackupFile.split('/')[0];
} else {
const folderName = blob.folderName;
if (!folderName || folderName === '/' || folderName === 'undefined') {
return blobContainerName;
}
// for online scenario, take the explicitly provided folder name
return blobContainerName + '/' + folderName;
}
}
export function getLastBackupFileNameWithoutFolder(blob: Blob) {
const lastBackupFile = blob.lastBackupFile;
if (!lastBackupFile || lastBackupFile.split('/').length !== 2) {
return lastBackupFile;
}
return lastBackupFile.split('/')[1];
}
export function getAzureResourceDropdownValues(
azureResources: { location: string, id: string, name: string }[],
location: azureResource.AzureLocation | undefined,
@@ -762,12 +836,16 @@ export function getResourceDropdownValues(resources: { id: string, name: string
|| [{ name: '', displayName: resourceNotFoundMessage }];
}
export async function getAzureTenantsDropdownValues(tenants: Tenant[]): Promise<CategoryValue[]> {
export function getAzureTenantsDropdownValues(tenants: Tenant[]): CategoryValue[] {
if (!tenants || !tenants.length) {
return [{ name: '', displayName: constants.ACCOUNT_SELECTION_PAGE_NO_LINKED_ACCOUNTS_ERROR }];
}
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[]> {
export function getAzureLocationsDropdownValues(locations: azureResource.AzureLocation[]): CategoryValue[] {
if (!locations || !locations.length) {
return [{ name: '', displayName: constants.NO_LOCATION_FOUND }];
}
@@ -776,11 +854,24 @@ export async function getAzureLocationsDropdownValues(locations: azureResource.A
|| [{ name: '', displayName: constants.NO_LOCATION_FOUND }];
}
export async function getBlobLastBackupFileNamesValues(blobs: azureResource.Blob[]): Promise<CategoryValue[]> {
export function getBlobLastBackupFileNamesValues(blobs: azureResource.Blob[]): CategoryValue[] {
if (!blobs || !blobs.length) {
return [{ name: '', displayName: constants.NO_BLOBFILES_FOUND }];
}
return blobs?.map(blob => { return { name: blob.name, displayName: blob.name }; })
|| [{ name: '', displayName: constants.NO_BLOBFILES_FOUND }];
}
export function getBlobFolderValues(folders: string[]): CategoryValue[] {
if (!folders || !folders.length) {
return [{ name: '', displayName: constants.NO_BLOBFOLDERS_FOUND }];
}
return folders?.map(folder => { return { name: folder, displayName: folder }; })
|| [{ name: '', displayName: constants.NO_BLOBFOLDERS_FOUND }];
}
export async function updateControlDisplay(control: Component, visible: boolean, displayStyle: DisplayType = 'inline'): Promise<void> {
const display = visible ? displayStyle : 'none';
control.display = display;