diff --git a/packages/techdocs-common/__mocks__/@azure/storage-blob.ts b/packages/techdocs-common/__mocks__/@azure/storage-blob.ts index 73c5873832..56574dedf7 100644 --- a/packages/techdocs-common/__mocks__/@azure/storage-blob.ts +++ b/packages/techdocs-common/__mocks__/@azure/storage-blob.ts @@ -125,6 +125,10 @@ export class ContainerClient { getBlockBlobClient(blobName: string) { return new BlockBlobClient(blobName); } + + listBlobsFlat() { + return []; + } } class ContainerClientFailGetProperties extends ContainerClient { diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts index e55314a71b..8c31153201 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts @@ -64,10 +64,11 @@ const getEntityRootDir = (entity: Entity) => { const logger = getVoidLogger(); jest.spyOn(logger, 'info').mockReturnValue(logger); -jest.spyOn(logger, 'error').mockReturnValue(logger); +const loggerSpy = jest.spyOn(logger, 'error').mockReturnValue(logger); let publisher: PublisherBase; beforeEach(async () => { + loggerSpy.mockClear(); mockFs.restore(); const mockConfig = new ConfigReader({ techdocs: { @@ -175,14 +176,12 @@ describe('publishing with valid credentials', () => { directory: wrongPathToGeneratedDirectory, }); - await expect(fails).rejects.toMatchObject({ - message: expect.stringContaining( - `Unable to upload file(s) to Azure Blob Storage. Error: Failed to read template directory: ENOENT, no such file or directory`, - ), - }); - await expect(fails).rejects.toMatchObject({ - message: expect.stringContaining(wrongPathToGeneratedDirectory), - }); + await expect(fails).rejects.toThrow( + /Unable to upload file\(s\) to Azure. Error: Failed to read template directory: ENOENT, no such file or directory/, + ); + await expect(fails).rejects.toThrow( + new RegExp(wrongPathToGeneratedDirectory), + ); mockFs.restore(); }); @@ -225,13 +224,11 @@ describe('publishing with valid credentials', () => { error = e; } - expect(error.message).toContain( - `Unable to upload file(s) to Azure Blob Storage.`, - ); + expect(error.message).toContain(`Unable to upload file(s) to Azure`); expect(logger.error).toHaveBeenCalledWith( expect.stringContaining( - `Unable to upload file(s) to Azure Blob Storage. Error: Upload failed for ${path.join( + `Unable to upload file(s) to Azure. Error: Upload failed for ${path.join( entityRootDir, 'index.html', )} with status code 500`, diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts index 5dc868ba2b..c7b86ed39e 100644 --- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts +++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.ts @@ -16,6 +16,7 @@ import { DefaultAzureCredential } from '@azure/identity'; import { BlobServiceClient, + ContainerClient, StorageSharedKeyCredential, } from '@azure/storage-blob'; import { Entity, EntityName } from '@backstage/catalog-model'; @@ -139,8 +140,9 @@ export class AzureBlobStoragePublish implements PublisherBase { // First, retrieve a list of all individual files in currently existing const remoteFolder = getCloudPathForLocalPath(entity); const existingFiles: string[] = []; - const container = this.storageClient.getContainerClient(this.containerName); + let container: ContainerClient; try { + container = this.storageClient.getContainerClient(this.containerName); for await (const blob of container.listBlobsFlat()) { existingFiles.push(blob.name); } @@ -158,23 +160,42 @@ export class AzureBlobStoragePublish implements PublisherBase { // e.g. ['index.html', 'sub-page/index.html', 'assets/images/favicon.png'] absoluteFilesToUpload = await getFileTreeRecursively(directory); + const failedOperations: Error[] = []; await bulkStorageOperation( async absoluteFilePath => { // const relativeFilePath = path.relative(directory, absoluteFilePath); const relativeFilePath = path.normalize( path.relative(directory, absoluteFilePath), ); - return await this.storageClient - .getContainerClient(this.containerName) + const response = await container .getBlockBlobClient( getCloudPathForLocalPath(entity, relativeFilePath), ) .uploadFile(absoluteFilePath); + + if (response._response.status >= 400) { + failedOperations.push( + new Error( + `Upload failed for ${absoluteFilePath} with status code ${response._response.status}`, + ), + ); + } + + return response; }, absoluteFilesToUpload, { concurrencyLimit: BATCH_CONCURRENCY }, ); + if (failedOperations.length > 0) { + throw new Error( + failedOperations + .map(r => r.message) + .filter(Boolean) + .join(' '), + ); + } + this.logger.info( `Successfully uploaded all the generated files for Entity ${entity.metadata.name}. Total number of files: ${absoluteFilesToUpload.length}`, );