diff --git a/package-lock.json b/package-lock.json index a2ca35f1..6caf4f21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@azure/data-tables": "^13.0.0", "@azure/storage-blob": "^12.1.1", "@azure/storage-file-datalake": "^12.1.1", - "@azure/storage-file-share": "^12.1.1", + "@azure/storage-file-share": "^12.25.0-beta.1", "@azure/storage-queue": "^12.7.0", "@microsoft/vscode-azext-azureappservice": "^0.7.0", "@microsoft/vscode-azext-azureutils": "^2.0.1", @@ -346,6 +346,30 @@ "node": ">=14.0.0" } }, + "node_modules/@azure/core-http-compat": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.1.2.tgz", + "integrity": "sha512-5MnV1yqzZwgNLLjlizsU3QqOeQChkIXw781Fwh1xdAqJR5AA32IUaq6xv1BICJvfbHoa+JYcaij2HFkhLbNTJQ==", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-client": "^1.3.0", + "@azure/core-rest-pipeline": "^1.3.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-http-compat/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@azure/core-http/node_modules/@azure/core-tracing": { "version": "1.0.0-preview.13", "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", @@ -403,26 +427,37 @@ } }, "node_modules/@azure/core-tracing": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", - "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.1.2.tgz", + "integrity": "sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA==", "dependencies": { - "tslib": "^2.2.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" } }, "node_modules/@azure/core-util": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.2.tgz", - "integrity": "sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.2.tgz", + "integrity": "sha512-l1Qrqhi4x1aekkV+OlcqsJa4AnAkj5p0JV8omgwjaV9OAbP41lvrMvs+CptfetKkeEaGRGSzby7sjPZEX7+kkQ==", "dependencies": { - "@azure/abort-controller": "^1.0.0", - "tslib": "^2.2.0" + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-util/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" } }, "node_modules/@azure/core-xml": { @@ -581,32 +616,36 @@ } }, "node_modules/@azure/storage-file-share": { - "version": "12.14.0", - "resolved": "https://registry.npmjs.org/@azure/storage-file-share/-/storage-file-share-12.14.0.tgz", - "integrity": "sha512-gTiA+MiRPGV77u9cyG8Px5P7X/XLBGYj6/PU6ldAeu9Poo0+ivushz1dOzPLZuSL6BhyKnh7RL2DolxUyHaMGA==", + "version": "12.25.0-beta.1", + "resolved": "https://registry.npmjs.org/@azure/storage-file-share/-/storage-file-share-12.25.0-beta.1.tgz", + "integrity": "sha512-ACdNDzYid7kASeJPuYysN6PzYsO7FocDvnSbEOW/xbjF49Rj+c/R0cIq+G2/wr8rj2VvMQxgw/Py0UBheQko1w==", "dependencies": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-http": "^3.0.0", + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.4.0", + "@azure/core-client": "^1.6.2", + "@azure/core-http-compat": "^2.0.0", "@azure/core-paging": "^1.1.1", - "@azure/core-tracing": "1.0.0-preview.13", + "@azure/core-rest-pipeline": "^1.10.1", + "@azure/core-tracing": "^1.1.2", + "@azure/core-util": "^1.6.1", + "@azure/core-xml": "^1.3.2", "@azure/logger": "^1.0.0", "events": "^3.0.0", "tslib": "^2.2.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.0.0" } }, - "node_modules/@azure/storage-file-share/node_modules/@azure/core-tracing": { - "version": "1.0.0-preview.13", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", - "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==", + "node_modules/@azure/storage-file-share/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", "dependencies": { - "@opentelemetry/api": "^1.0.1", - "tslib": "^2.2.0" + "tslib": "^2.6.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.0.0" } }, "node_modules/@azure/storage-queue": { @@ -9484,9 +9523,9 @@ } }, "node_modules/tslib": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", - "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -10897,6 +10936,26 @@ } } }, + "@azure/core-http-compat": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.1.2.tgz", + "integrity": "sha512-5MnV1yqzZwgNLLjlizsU3QqOeQChkIXw781Fwh1xdAqJR5AA32IUaq6xv1BICJvfbHoa+JYcaij2HFkhLbNTJQ==", + "requires": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-client": "^1.3.0", + "@azure/core-rest-pipeline": "^1.3.0" + }, + "dependencies": { + "@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "requires": { + "tslib": "^2.6.2" + } + } + } + }, "@azure/core-lro": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.3.tgz", @@ -10933,20 +10992,30 @@ } }, "@azure/core-tracing": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", - "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.1.2.tgz", + "integrity": "sha512-dawW9ifvWAWmUm9/h+/UQ2jrdvjCJ7VJEuCJ6XVNudzcOwm53BFZH4Q845vjfgoUAM8ZxokvVNxNxAITc502YA==", "requires": { - "tslib": "^2.2.0" + "tslib": "^2.6.2" } }, "@azure/core-util": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.2.tgz", - "integrity": "sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.9.2.tgz", + "integrity": "sha512-l1Qrqhi4x1aekkV+OlcqsJa4AnAkj5p0JV8omgwjaV9OAbP41lvrMvs+CptfetKkeEaGRGSzby7sjPZEX7+kkQ==", "requires": { - "@azure/abort-controller": "^1.0.0", - "tslib": "^2.2.0" + "@azure/abort-controller": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "requires": { + "tslib": "^2.6.2" + } + } } }, "@azure/core-xml": { @@ -11089,26 +11158,30 @@ } }, "@azure/storage-file-share": { - "version": "12.14.0", - "resolved": "https://registry.npmjs.org/@azure/storage-file-share/-/storage-file-share-12.14.0.tgz", - "integrity": "sha512-gTiA+MiRPGV77u9cyG8Px5P7X/XLBGYj6/PU6ldAeu9Poo0+ivushz1dOzPLZuSL6BhyKnh7RL2DolxUyHaMGA==", + "version": "12.25.0-beta.1", + "resolved": "https://registry.npmjs.org/@azure/storage-file-share/-/storage-file-share-12.25.0-beta.1.tgz", + "integrity": "sha512-ACdNDzYid7kASeJPuYysN6PzYsO7FocDvnSbEOW/xbjF49Rj+c/R0cIq+G2/wr8rj2VvMQxgw/Py0UBheQko1w==", "requires": { - "@azure/abort-controller": "^1.0.0", - "@azure/core-http": "^3.0.0", + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.4.0", + "@azure/core-client": "^1.6.2", + "@azure/core-http-compat": "^2.0.0", "@azure/core-paging": "^1.1.1", - "@azure/core-tracing": "1.0.0-preview.13", + "@azure/core-rest-pipeline": "^1.10.1", + "@azure/core-tracing": "^1.1.2", + "@azure/core-util": "^1.6.1", + "@azure/core-xml": "^1.3.2", "@azure/logger": "^1.0.0", "events": "^3.0.0", "tslib": "^2.2.0" }, "dependencies": { - "@azure/core-tracing": { - "version": "1.0.0-preview.13", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.0-preview.13.tgz", - "integrity": "sha512-KxDlhXyMlh2Jhj2ykX6vNEU0Vou4nHr025KoSEiz7cS3BNiHNaZcdECk/DmLkEB0as5T7b/TpRcehJ5yV6NeXQ==", + "@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", "requires": { - "@opentelemetry/api": "^1.0.1", - "tslib": "^2.2.0" + "tslib": "^2.6.2" } } } @@ -17971,9 +18044,9 @@ } }, "tslib": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", - "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "tsutils": { "version": "3.21.0", diff --git a/package.json b/package.json index 355962b4..a8b69484 100644 --- a/package.json +++ b/package.json @@ -840,7 +840,7 @@ "@azure/data-tables": "^13.0.0", "@azure/storage-blob": "^12.1.1", "@azure/storage-file-datalake": "^12.1.1", - "@azure/storage-file-share": "^12.1.1", + "@azure/storage-file-share": "^12.25.0-beta.1", "@azure/storage-queue": "^12.7.0", "@microsoft/vscode-azext-azureappservice": "^0.7.0", "@microsoft/vscode-azext-azureutils": "^2.0.1", diff --git a/src/AttachedAccountRoot.ts b/src/AttachedAccountRoot.ts index 3c75e89b..37bf3c89 100644 --- a/src/AttachedAccountRoot.ts +++ b/src/AttachedAccountRoot.ts @@ -13,7 +13,7 @@ export class AttachedAccountRoot implements ISubscriptionContext { throw this._error; } - public createCredentialsForScopes(): never { + public get createCredentialsForScopes(): never { throw this._error; } diff --git a/src/AzureStorageFS.ts b/src/AzureStorageFS.ts index 5e31c9bd..97f0ed4c 100644 --- a/src/AzureStorageFS.ts +++ b/src/AzureStorageFS.ts @@ -78,9 +78,9 @@ export class AzureStorageFS implements vscode.FileSystemProvider, vscode.TextDoc static async showEditor(context: IActionContext, treeItem: BlobTreeItem | FileTreeItem): Promise { let client: BlockBlobClient | ShareFileClient; if (treeItem instanceof BlobTreeItem) { - client = createBlockBlobClient(treeItem.root, treeItem.container.name, treeItem.blobPath); + client = await createBlockBlobClient(treeItem.root, treeItem.container.name, treeItem.blobPath); } else { - client = createFileClient(treeItem.root, treeItem.shareName, treeItem.directoryPath, treeItem.fileName); + client = await createFileClient(treeItem.root, treeItem.shareName, treeItem.directoryPath, treeItem.fileName); } const uri = this.idToUri(treeItem.fullId); @@ -137,10 +137,10 @@ export class AzureStorageFS implements vscode.FileSystemProvider, vscode.TextDoc let props: (BlobGetPropertiesResponse & FileGetPropertiesResponse) | undefined; try { if (treeItem instanceof BlobTreeItem) { - const blockBlobClient: BlockBlobClient = createBlockBlobClient(treeItem.root, treeItem.container.name, treeItem.blobPath); + const blockBlobClient: BlockBlobClient = await createBlockBlobClient(treeItem.root, treeItem.container.name, treeItem.blobPath); props = await blockBlobClient.getProperties(); } else if (treeItem instanceof FileTreeItem) { - const fileClient: ShareFileClient = createFileClient(treeItem.root, treeItem.shareName, treeItem.directoryPath, treeItem.fileName); + const fileClient: ShareFileClient = await createFileClient(treeItem.root, treeItem.shareName, treeItem.directoryPath, treeItem.fileName); props = await fileClient.getProperties(); } } catch (error) { @@ -241,10 +241,10 @@ export class AzureStorageFS implements vscode.FileSystemProvider, vscode.TextDoc try { let buffer: Buffer; if (treeItem instanceof FileShareTreeItem) { - client = createFileClient(treeItem.root, treeItem.shareName, parsedUri.parentDirPath, parsedUri.baseName); + client = await createFileClient(treeItem.root, treeItem.shareName, parsedUri.parentDirPath, parsedUri.baseName); buffer = await client.downloadToBuffer(); } else { - client = createBlobClient(treeItem.root, treeItem.container.name, parsedUri.filePath); + client = await createBlobClient(treeItem.root, treeItem.container.name, parsedUri.filePath); buffer = await client.downloadToBuffer(); } return buffer; diff --git a/src/BlobContainerFS.ts b/src/BlobContainerFS.ts index a969b06d..26ac3cb4 100644 --- a/src/BlobContainerFS.ts +++ b/src/BlobContainerFS.ts @@ -196,7 +196,7 @@ export class BlobContainerFS implements vscode.FileSystemProvider { } static async showEditor(context: IActionContext, treeItem: BlobTreeItem): Promise { - const client: BlockBlobClient = createBlockBlobClient(treeItem.root, treeItem.container.name, treeItem.blobPath); + const client: BlockBlobClient = await createBlockBlobClient(treeItem.root, treeItem.container.name, treeItem.blobPath); const uri = BlobContainerFS.idToUri(treeItem.fullId); const properties: BlobGetPropertiesResponse = await client.getProperties(); diff --git a/src/commands/deleteBlob/DeleteBlobStep.ts b/src/commands/deleteBlob/DeleteBlobStep.ts index 5fc5ef11..b6458963 100644 --- a/src/commands/deleteBlob/DeleteBlobStep.ts +++ b/src/commands/deleteBlob/DeleteBlobStep.ts @@ -21,7 +21,7 @@ export class DeleteBlobStep extends AzureWizardExecuteStep { // Does NOT update treeItem's _webHostingEnabled. - const serviceClient: BlobServiceClient = this.root.createBlobServiceClient(); + const serviceClient: BlobServiceClient = await this.root.createBlobServiceClient(); const properties: ServiceGetPropertiesResponse = await serviceClient.getProperties(); const staticWebsite: StaticWebsite | undefined = properties.staticWebsite; @@ -137,19 +137,19 @@ class AttachedStorageRoot extends AttachedAccountRoot { ).toString(); } - public createBlobServiceClient(): BlobServiceClient { + public async createBlobServiceClient(): Promise { return BlobServiceClient.fromConnectionString(this._connectionString, this._serviceClientPipelineOptions); } - public createShareServiceClient(): ShareServiceClient { + public async createShareServiceClient(): Promise { return ShareServiceClient.fromConnectionString(this._connectionString, this._serviceClientPipelineOptions); } - public createQueueServiceClient(): QueueServiceClient { + public async createQueueServiceClient(): Promise { return QueueServiceClient.fromConnectionString(this._connectionString, this._serviceClientPipelineOptions); } - public createTableServiceClient(): TableServiceClient { + public async createTableServiceClient(): Promise { return TableServiceClient.fromConnectionString(this._connectionString, { retryOptions: { maxRetries: this._serviceClientPipelineOptions.retryOptions.maxTries } }); } } diff --git a/src/tree/IStorageRoot.ts b/src/tree/IStorageRoot.ts index 933b48fc..dd0fcdbe 100644 --- a/src/tree/IStorageRoot.ts +++ b/src/tree/IStorageRoot.ts @@ -16,8 +16,8 @@ export interface IStorageRoot { isEmulated: boolean; primaryEndpoints?: Endpoints; generateSasToken(accountSASSignatureValues: AccountSASSignatureValuesBlob | AccountSASSignatureValuesFileShare): string; - createBlobServiceClient(): BlobServiceClient; - createShareServiceClient(): ShareServiceClient; - createQueueServiceClient(): QueueServiceClient; - createTableServiceClient(): TableServiceClient; + createBlobServiceClient(): Promise; + createShareServiceClient(): Promise; + createQueueServiceClient(): Promise; + createTableServiceClient(): Promise; } diff --git a/src/tree/StorageAccountTreeItem.ts b/src/tree/StorageAccountTreeItem.ts index 273adaf6..5e0aef3b 100644 --- a/src/tree/StorageAccountTreeItem.ts +++ b/src/tree/StorageAccountTreeItem.ts @@ -68,7 +68,8 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT private constructor( private readonly _subscription: ISubscriptionContext, public readonly storageAccount: StorageAccountWrapper, - public readonly storageManagementClient: StorageManagementClient) { + public readonly storageManagementClient: StorageManagementClient, + ) { this._root = this.createRoot(); } @@ -89,7 +90,6 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT // eslint-disable-next-line @typescript-eslint/require-await async loadMoreChildrenImpl(_clearCache: boolean): Promise { - this._blobContainerGroupTreeItem = new BlobContainerGroupTreeItem(this as unknown as (AzExtParentTreeItem & ResolvedAppResourceTreeItem)); this._fileShareGroupTreeItem = new FileShareGroupTreeItem(this as unknown as (AzExtParentTreeItem & ResolvedAppResourceTreeItem)); this._queueGroupTreeItem = new QueueGroupTreeItem(this as unknown as (AzExtParentTreeItem & ResolvedAppResourceTreeItem)); @@ -165,21 +165,57 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT new StorageSharedKeyCredentialBlob(this.storageAccount.name, this.key.value) ).toString(); }, - createBlobServiceClient: () => { + createBlobServiceClient: async () => { const credential = new StorageSharedKeyCredentialBlob(this.storageAccount.name, this.key.value); - return new BlobServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'), credential); + let client = new BlobServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'), credential); + try { + await client.getProperties(); // Trigger a request to validate the key + } catch (error) { + const token = await this._subscription.createCredentialsForScopes(['https://storage.azure.com/.default']); + client = new BlobServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'), token); + await client.getProperties(); // Trigger a request to validate the token + } + + return client; }, - createShareServiceClient: () => { + createShareServiceClient: async () => { const credential = new StorageSharedKeyCredentialFileShare(this.storageAccount.name, this.key.value); - return new ShareServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), credential); + let client = new ShareServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), credential); + try { + await client.getProperties(); // Trigger a request to validate the key + } catch (error) { + const token = await this._subscription.createCredentialsForScopes(['https://storage.azure.com/.default']); + client = new ShareServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), token, { fileRequestIntent: 'backup' }); + await client.getProperties(); // Trigger a request to validate the token + } + + return client; }, - createQueueServiceClient: () => { + createQueueServiceClient: async () => { const credential = new StorageSharedKeyCredentialQueue(this.storageAccount.name, this.key.value); - return new QueueServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'), credential); + let client = new QueueServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'), credential); + try { + await client.getProperties(); // Trigger a request to validate the key + } catch (error) { + const token = await this._subscription.createCredentialsForScopes(['https://storage.azure.com/.default']); + client = new QueueServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'), token); + await client.getProperties(); // Trigger a request to validate the token + } + + return client; }, - createTableServiceClient: () => { + createTableServiceClient: async () => { const credential = new AzureNamedKeyCredential(this.storageAccount.name, this.key.value); - return new TableServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'table'), credential); + let client = new TableServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'table'), credential); + try { + await client.getProperties(); // Trigger a request to validate the key + } catch (error) { + const token = await this._subscription.createCredentialsForScopes(['https://storage.azure.com/.default']); + client = new TableServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'table'), token); + await client.getProperties(); // Trigger a request to validate the token + } + + return client; } }; } @@ -243,7 +279,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT public async getActualWebsiteHostingStatus(): Promise { // Does NOT update treeItem's _webHostingEnabled. - const serviceClient: BlobServiceClient = this.root.createBlobServiceClient(); + const serviceClient: BlobServiceClient = await this.root.createBlobServiceClient(); const properties: ServiceGetPropertiesResponse = await serviceClient.getProperties(); const staticWebsite: StaticWebsite | undefined = properties.staticWebsite; @@ -256,12 +292,12 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT } public async setWebsiteHostingProperties(properties: BlobServiceProperties): Promise { - const serviceClient: BlobServiceClient = this.root.createBlobServiceClient(); + const serviceClient: BlobServiceClient = await this.root.createBlobServiceClient(); await serviceClient.setProperties(properties); } private async getAccountType(): Promise { - const serviceClient: BlobServiceClient = this.root.createBlobServiceClient(); + const serviceClient: BlobServiceClient = await this.root.createBlobServiceClient(); const accountType: AccountKind | undefined = (await serviceClient.getAccountInfo()).accountKind; if (!accountType) { diff --git a/src/tree/blob/BlobContainerGroupTreeItem.ts b/src/tree/blob/BlobContainerGroupTreeItem.ts index d4c6ba9f..76d7f18e 100644 --- a/src/tree/blob/BlobContainerGroupTreeItem.ts +++ b/src/tree/blob/BlobContainerGroupTreeItem.ts @@ -82,7 +82,7 @@ export class BlobContainerGroupTreeItem extends AzExtParentTreeItem implements I } async listContainers(continuationToken?: string): Promise { - const blobServiceClient: BlobServiceClient = this.root.createBlobServiceClient(); + const blobServiceClient: BlobServiceClient = await this.root.createBlobServiceClient(); const response: AsyncIterableIterator = blobServiceClient.listContainers().byPage({ continuationToken, maxPageSize: maxPageSize }); // eslint-disable-next-line @typescript-eslint/no-unsafe-return @@ -108,7 +108,7 @@ export class BlobContainerGroupTreeItem extends AzExtParentTreeItem implements I } private async createBlobContainer(name: string): Promise { - const containerClient: ContainerClient = createBlobContainerClient(this.root, name); + const containerClient: ContainerClient = await createBlobContainerClient(this.root, name); await containerClient.create(); const containersResponse: ListContainersSegmentResponse = await this.listContainers(); diff --git a/src/tree/blob/BlobContainerTreeItem.ts b/src/tree/blob/BlobContainerTreeItem.ts index 822a7064..9a11e049 100644 --- a/src/tree/blob/BlobContainerTreeItem.ts +++ b/src/tree/blob/BlobContainerTreeItem.ts @@ -48,7 +48,8 @@ export class BlobContainerTreeItem extends AzExtParentTreeItem implements ICopyU private constructor( parent: BlobContainerGroupTreeItem, - public readonly container: ContainerItem) { + public readonly container: ContainerItem, + public readonly resourceUri: string) { super(parent); } @@ -60,11 +61,6 @@ export class BlobContainerTreeItem extends AzExtParentTreeItem implements ICopyU return ''; } - public get resourceUri(): string { - const containerClient: ContainerClient = this.root.createBlobServiceClient().getContainerClient(this.container.name); - return containerClient.url; - } - public get transferSasToken(): string { const accountSASSignatureValues: AccountSASSignatureValues = { expiresOn: new Date(Date.now() + threeDaysInMS), @@ -76,7 +72,8 @@ export class BlobContainerTreeItem extends AzExtParentTreeItem implements ICopyU } public static async createBlobContainerTreeItem(parent: BlobContainerGroupTreeItem, container: ContainerItem): Promise { - const ti = new BlobContainerTreeItem(parent, container); + const containerClient: ContainerClient = await createBlobContainerClient(parent.root, container.name); + const ti = new BlobContainerTreeItem(parent, container, containerClient.url); // Get static website status to display the appropriate icon await ti.refreshImpl(); return ti; @@ -139,7 +136,7 @@ export class BlobContainerTreeItem extends AzExtParentTreeItem implements ICopyU let response: AsyncIterableIterator; let responseValue: ListBlobsFlatSegmentResponse; const blobs: BlobItem[] = []; - const containerClient: ContainerClient = createBlobContainerClient(this.root, this.container.name); + const containerClient: ContainerClient = await createBlobContainerClient(this.root, this.container.name); ext.outputChannel.appendLog(`Querying Azure... Method: listBlobsFlat blobContainerName: "${this.container.name}" prefix: ""`); @@ -165,7 +162,7 @@ export class BlobContainerTreeItem extends AzExtParentTreeItem implements ICopyU const message: string = `Are you sure you want to delete blob container '${this.label}' and all its contents?`; const result = await context.ui.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel); if (result === DialogResponses.deleteResponse) { - const containerClient: ContainerClient = createBlobContainerClient(this.root, this.container.name); + const containerClient: ContainerClient = await createBlobContainerClient(this.root, this.container.name); await containerClient.delete(); } @@ -177,9 +174,11 @@ export class BlobContainerTreeItem extends AzExtParentTreeItem implements ICopyU if (context.remoteFilePath && context.localFilePath) { context.showCreatingTreeItem(context.remoteFilePath); await this.uploadLocalFile(context, context.localFilePath, context.remoteFilePath); - child = new BlobTreeItem(this, context.remoteFilePath, this.container); + const containerClient = await createBlobContainerClient(this.root, this.container.name); + child = new BlobTreeItem(this, context.remoteFilePath, this.container, containerClient.url); } else if ((context.childName !== undefined) && context.childType === BlobDirectoryTreeItem.contextValue) { - child = new BlobDirectoryTreeItem(this, context.childName, this.container); + const containerClient = await createBlobContainerClient(this.root, this.container.name); + child = new BlobDirectoryTreeItem(this, context.childName, this.container, containerClient.url); } else { child = await createChildAsNewBlockBlob(this, context); } @@ -189,8 +188,7 @@ export class BlobContainerTreeItem extends AzExtParentTreeItem implements ICopyU } public getUrl(): string { - const containerClient: ContainerClient = createBlobContainerClient(this.root, this.container.name); - return containerClient.url; + return this.resourceUri; } public async copyUrl(): Promise { @@ -324,7 +322,7 @@ export class BlobContainerTreeItem extends AzExtParentTreeItem implements ICopyU cancellationToken: vscode.CancellationToken, properties: TelemetryProperties, ): Promise { - const containerClient: ContainerClient = createBlobContainerClient(this.root, this.container.name); + const containerClient: ContainerClient = await createBlobContainerClient(this.root, this.container.name); for (const blobIndex of blobsToDelete.keys()) { const blob: BlobItem = blobsToDelete[blobIndex]; try { diff --git a/src/tree/blob/BlobDirectoryTreeItem.ts b/src/tree/blob/BlobDirectoryTreeItem.ts index 567ff4ba..33957b78 100644 --- a/src/tree/blob/BlobDirectoryTreeItem.ts +++ b/src/tree/blob/BlobDirectoryTreeItem.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { AccountSASSignatureValues, BlobClient, ContainerClient, ContainerItem } from "@azure/storage-blob"; +import type { AccountSASSignatureValues, BlobClient, ContainerItem } from "@azure/storage-blob"; import { polyfill } from '../../polyfill.worker'; polyfill(); @@ -44,7 +44,11 @@ export class BlobDirectoryTreeItem extends AzExtParentTreeItem implements ICopyU private _continuationToken: string | undefined; - constructor(parent: BlobContainerTreeItem | BlobDirectoryTreeItem, dirPath: string, public container: ContainerItem) { + constructor( + parent: BlobContainerTreeItem | BlobDirectoryTreeItem, + dirPath: string, + public container: ContainerItem, + public readonly resourceUri: string) { super(parent); if (!dirPath.endsWith(path.posix.sep)) { dirPath += path.posix.sep; @@ -70,11 +74,6 @@ export class BlobDirectoryTreeItem extends AzExtParentTreeItem implements ICopyU return new vscode.ThemeIcon('folder'); } - public get resourceUri(): string { - const containerClient: ContainerClient = this.root.createBlobServiceClient().getContainerClient(this.container.name); - return containerClient.url; - } - public get transferSasToken(): string { const accountSASSignatureValues: AccountSASSignatureValues = { expiresOn: new Date(Date.now() + threeDaysInMS), @@ -104,14 +103,14 @@ export class BlobDirectoryTreeItem extends AzExtParentTreeItem implements ICopyU if (context.childType === BlobTreeItem.contextValue) { child = await createChildAsNewBlockBlob(this, context); } else { - child = new BlobDirectoryTreeItem(this, path.posix.join(this.dirPath, context.childName), this.container); + child = new BlobDirectoryTreeItem(this, path.posix.join(this.dirPath, context.childName), this.container, this.resourceUri); } AzureStorageFS.fireCreateEvent(child); return child; } public async copyUrl(): Promise { - const blobClient: BlobClient = createBlobClient(this.root, this.container.name, this.dirPath); + const blobClient: BlobClient = await createBlobClient(this.root, this.container.name, this.dirPath); const url = blobClient.url; await copyAndShowToast(url, 'Blob Directory URL'); } diff --git a/src/tree/blob/BlobTreeItem.ts b/src/tree/blob/BlobTreeItem.ts index 927a19dc..2b9338b7 100644 --- a/src/tree/blob/BlobTreeItem.ts +++ b/src/tree/blob/BlobTreeItem.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.md in the project root for license information. **/ -import type { AccountSASSignatureValues, BlobClient, BlobGetPropertiesResponse, BlockBlobClient, ContainerClient, ContainerItem } from "@azure/storage-blob"; +import type { AccountSASSignatureValues, BlobClient, BlobGetPropertiesResponse, BlockBlobClient, ContainerItem } from "@azure/storage-blob"; import { polyfill } from '../../polyfill.worker'; polyfill(); @@ -43,7 +43,11 @@ export class BlobTreeItem extends AzExtTreeItem implements ICopyUrl, ITransferSr */ public readonly blobPath: string; - constructor(parent: BlobContainerTreeItem | BlobDirectoryTreeItem, blobPath: string, public readonly container: ContainerItem) { + constructor(parent: BlobContainerTreeItem | BlobDirectoryTreeItem, + blobPath: string, + public readonly container: ContainerItem, + public readonly resourceUri: string + ) { super(parent); this.commandId = 'azureStorage.editBlob'; this.blobPath = blobPath; @@ -66,11 +70,6 @@ export class BlobTreeItem extends AzExtTreeItem implements ICopyUrl, ITransferSr return new vscode.ThemeIcon('file'); } - public get resourceUri(): string { - const containerClient: ContainerClient = this.root.createBlobServiceClient().getContainerClient(this.container.name); - return containerClient.url; - } - public get transferSasToken(): string { const accountSASSignatureValues: AccountSASSignatureValues = { expiresOn: new Date(Date.now() + threeDaysInMS), @@ -83,7 +82,7 @@ export class BlobTreeItem extends AzExtTreeItem implements ICopyUrl, ITransferSr public async copyUrl(): Promise { // Use this.blobPath here instead of this.blobName. Otherwise the blob's containing directory/directories aren't displayed - const blobClient: BlobClient = createBlobClient(this.root, this.container.name, this.blobPath); + const blobClient: BlobClient = await createBlobClient(this.root, this.container.name, this.blobPath); const url = blobClient.url; await copyAndShowToast(url, 'Blob URL'); } @@ -106,14 +105,14 @@ export class BlobTreeItem extends AzExtTreeItem implements ICopyUrl, ITransferSr await wizard.prompt(); await wizard.execute(); } else { - const blobClient: BlobClient = createBlobClient(this.root, this.container.name, this.blobPath); + const blobClient: BlobClient = await createBlobClient(this.root, this.container.name, this.blobPath); await blobClient.delete(); } AzureStorageFS.fireDeleteEvent(this); } public async checkCanDownload(context: IActionContext): Promise { - const client: BlockBlobClient = createBlockBlobClient(this.root, this.container.name, this.blobPath); + const client: BlockBlobClient = await createBlockBlobClient(this.root, this.container.name, this.blobPath); const props: BlobGetPropertiesResponse = await client.getProperties(); context.telemetry.measurements.blobDownloadSize = props.contentLength; if (props.blobType && !props.blobType.toLocaleLowerCase().startsWith("block")) { diff --git a/src/tree/fileShare/DirectoryTreeItem.ts b/src/tree/fileShare/DirectoryTreeItem.ts index ae5efba5..9ee693d4 100644 --- a/src/tree/fileShare/DirectoryTreeItem.ts +++ b/src/tree/fileShare/DirectoryTreeItem.ts @@ -20,7 +20,7 @@ import { threeDaysInMS } from '../../constants'; import { ext } from "../../extensionVariables"; import { copyAndShowToast } from '../../utils/copyAndShowToast'; import { askAndCreateChildDirectory, deleteDirectoryAndContents, listFilesInDirectory } from '../../utils/directoryUtils'; -import { askAndCreateEmptyTextFile, createDirectoryClient } from '../../utils/fileUtils'; +import { askAndCreateEmptyTextFile, createDirectoryClient, createFileClient } from '../../utils/fileUtils'; import { ICopyUrl } from '../ICopyUrl'; import { IStorageRoot } from '../IStorageRoot'; import { ITransferSrcOrDstTreeItem } from '../ITransferSrcOrDstTreeItem'; @@ -33,7 +33,8 @@ export class DirectoryTreeItem extends AzExtParentTreeItem implements ICopyUrl, parent: FileShareTreeItem | DirectoryTreeItem, public readonly parentPath: string, public readonly directoryName: string, // directoryName should not include parent path - public readonly shareName: string) { + public readonly shareName: string, + public readonly resourceUri: string) { super(parent); } @@ -58,11 +59,6 @@ export class DirectoryTreeItem extends AzExtParentTreeItem implements ICopyUrl, return path.posix.join(this.parentPath, this.directoryName); } - public get resourceUri(): string { - const shareClient = this.root.createShareServiceClient().getShareClient(this.shareName); - return shareClient.url; - } - public get transferSasToken(): string { const accountSASSignatureValues: AccountSASSignatureValues = { expiresOn: new Date(Date.now() + threeDaysInMS), @@ -85,18 +81,24 @@ export class DirectoryTreeItem extends AzExtParentTreeItem implements ICopyUrl, const { files, directories, continuationToken }: { files: FileItem[]; directories: DirectoryItem[]; continuationToken: string; } = await listFilesInDirectory(this.fullPath, this.shareName, this.root, this._continuationToken); this._continuationToken = continuationToken; + const fileTreeItems: FileTreeItem[] = await Promise.all(files.map(async (file: FileItem) => { + const shareClient = await createFileClient(this.root, this.shareName, this.directoryName, file.name); + return new FileTreeItem(this, file.name, this.fullPath, this.shareName, shareClient.url); + })); + + const directoryTreeItems: DirectoryTreeItem[] = await Promise.all(directories.map(async (directory: DirectoryItem) => { + const directoryClient = await createDirectoryClient(this.root, this.shareName, directory.name); + return new DirectoryTreeItem(this, this.fullPath, directory.name, this.shareName, directoryClient.url); + })); + return (<(DirectoryTreeItem | FileTreeItem)[]>[]) - .concat(files.map((file: FileItem) => { - return new FileTreeItem(this, file.name, this.fullPath, this.shareName); - })) - .concat(directories.map((directory: DirectoryItem) => { - return new DirectoryTreeItem(this, this.fullPath, directory.name, this.shareName); - })); + .concat(fileTreeItems) + .concat(directoryTreeItems); } public async copyUrl(): Promise { // Use this.fullPath here instead of this.directoryName. Otherwise only the leaf directory is displayed in the URL - const directoryClient: ShareDirectoryClient = createDirectoryClient(this.root, this.shareName, this.fullPath); + const directoryClient: ShareDirectoryClient = await createDirectoryClient(this.root, this.shareName, this.fullPath); const url = directoryClient.url; await copyAndShowToast(url, 'Directory URL'); } diff --git a/src/tree/fileShare/FileShareGroupTreeItem.ts b/src/tree/fileShare/FileShareGroupTreeItem.ts index 97aba8cd..ccdfcf25 100644 --- a/src/tree/fileShare/FileShareGroupTreeItem.ts +++ b/src/tree/fileShare/FileShareGroupTreeItem.ts @@ -49,7 +49,7 @@ export class FileShareGroupTreeItem extends AzExtParentTreeItem implements IStor this._continuationToken = undefined; } - const shareServiceClient: ShareServiceClient = this.root.createShareServiceClient(); + const shareServiceClient: ShareServiceClient = await this.root.createShareServiceClient(); const response: AsyncIterableIterator = shareServiceClient.listShares().byPage({ continuationToken: this._continuationToken, maxPageSize }); let responseValue: ServiceListSharesSegmentResponse; @@ -68,7 +68,7 @@ export class FileShareGroupTreeItem extends AzExtParentTreeItem implements IStor this._continuationToken = responseValue.continuationToken; return shares.map((share: ShareItem) => { - return new FileShareTreeItem(this, share.name); + return new FileShareTreeItem(this, share.name, shareServiceClient.getShareClient(share.name).url); }); } @@ -88,9 +88,9 @@ export class FileShareGroupTreeItem extends AzExtParentTreeItem implements IStor return await window.withProgress({ location: ProgressLocation.Window }, async (progress) => { context.showCreatingTreeItem(shareName); progress.report({ message: localize('creatingFileShare', 'Azure Storage: Creating file share "{0}"...', shareName) }); - const shareServiceClient: ShareServiceClient = this.root.createShareServiceClient(); + const shareServiceClient: ShareServiceClient = await this.root.createShareServiceClient(); await shareServiceClient.createShare(shareName, { quota }); - return new FileShareTreeItem(this, shareName); + return new FileShareTreeItem(this, shareName, shareServiceClient.getShareClient(shareName).url); }); } diff --git a/src/tree/fileShare/FileShareTreeItem.ts b/src/tree/fileShare/FileShareTreeItem.ts index 736c7b8a..b5d1e4f4 100644 --- a/src/tree/fileShare/FileShareTreeItem.ts +++ b/src/tree/fileShare/FileShareTreeItem.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { AccountSASSignatureValues, DirectoryItem, FileItem, ShareClient, ShareDirectoryClient } from '@azure/storage-file-share'; +import type { AccountSASSignatureValues, DirectoryItem, FileItem, ShareClient, ShareDirectoryClient, ShareServiceClient } from '@azure/storage-file-share'; import { polyfill } from '../../polyfill.worker'; polyfill(); @@ -34,7 +34,8 @@ export class FileShareTreeItem extends AzExtParentTreeItem implements ICopyUrl, constructor( parent: FileShareGroupTreeItem, - public readonly shareName: string) { + public readonly shareName: string, + public readonly resourceUri: string) { super(parent); this.iconPath = { light: path.join(getResourcesPath(), 'light', 'AzureFileShare.svg'), @@ -50,11 +51,6 @@ export class FileShareTreeItem extends AzExtParentTreeItem implements ICopyUrl, return ''; } - public get resourceUri(): string { - const shareClient = this.root.createShareServiceClient().getShareClient(this.shareName); - return shareClient.url; - } - public get transferSasToken(): string { const accountSASSignatureValues: AccountSASSignatureValues = { expiresOn: new Date(Date.now() + threeDaysInMS), @@ -90,11 +86,12 @@ export class FileShareTreeItem extends AzExtParentTreeItem implements ICopyUrl, const { files, directories, continuationToken }: { files: FileItem[]; directories: DirectoryItem[]; continuationToken: string; } = await listFilesInDirectory('', this.shareName, this.root, this._continuationToken); this._continuationToken = continuationToken; + const shareServiceClient = await this.root.createShareServiceClient(); return result.concat(directories.map((directory: DirectoryItem) => { - return new DirectoryTreeItem(this, '', directory.name, this.shareName); + return new DirectoryTreeItem(this, '', directory.name, this.shareName, shareServiceClient.getShareClient(this.shareName).url); })) .concat(files.map((file: FileItem) => { - return new FileTreeItem(this, file.name, '', this.shareName); + return new FileTreeItem(this, file.name, '', this.shareName, shareServiceClient.getShareClient(this.shareName).url); })); } @@ -109,8 +106,7 @@ export class FileShareTreeItem extends AzExtParentTreeItem implements ICopyUrl, } public getUrl(): string { - const shareClient: ShareClient = createShareClient(this.root, this.shareName); - return shareClient.url; + return this.resourceUri; } public async copyUrl(): Promise { @@ -122,7 +118,7 @@ export class FileShareTreeItem extends AzExtParentTreeItem implements ICopyUrl, const message: string = `Are you sure you want to delete file share '${this.label}' and all its contents?`; const result = await context.ui.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel); if (result === DialogResponses.deleteResponse) { - const shareClient: ShareClient = createShareClient(this.root, this.shareName); + const shareClient: ShareClient = await createShareClient(this.root, this.shareName); await shareClient.delete(); } else { throw new UserCancelledError(); @@ -136,7 +132,8 @@ export class FileShareTreeItem extends AzExtParentTreeItem implements ICopyUrl, if (context.remoteFilePath && context.localFilePath) { context.showCreatingTreeItem(context.remoteFilePath); await this.uploadLocalFile(context, context.localFilePath, context.remoteFilePath); - child = new FileTreeItem(this, context.remoteFilePath, '', this.shareName); + const shareServiceClient: ShareServiceClient = await this.root.createShareServiceClient(); + child = new FileTreeItem(this, context.remoteFilePath, '', this.shareName, shareServiceClient.getShareClient(this.shareName).url); } else if (context.childType === FileTreeItem.contextValue) { child = await askAndCreateEmptyTextFile(this, '', this.shareName, context); } else { @@ -161,7 +158,7 @@ export class FileShareTreeItem extends AzExtParentTreeItem implements ICopyUrl, for (const dir of parentDirectories) { partialParentDirectoryPath += `${dir}/`; if (!(await doesDirectoryExist(this, partialParentDirectoryPath, this.shareName))) { - const directoryClient: ShareDirectoryClient = createDirectoryClient(this.root, this.shareName, partialParentDirectoryPath); + const directoryClient: ShareDirectoryClient = await createDirectoryClient(this.root, this.shareName, partialParentDirectoryPath); await directoryClient.create(); } } diff --git a/src/tree/fileShare/FileTreeItem.ts b/src/tree/fileShare/FileTreeItem.ts index 1c95689b..1be69e54 100644 --- a/src/tree/fileShare/FileTreeItem.ts +++ b/src/tree/fileShare/FileTreeItem.ts @@ -30,7 +30,8 @@ export class FileTreeItem extends AzExtTreeItem implements ICopyUrl, ITransferSr parent: FileShareTreeItem | DirectoryTreeItem, public readonly fileName: string, public readonly directoryPath: string, - public readonly shareName: string) { + public readonly shareName: string, + public readonly resourceUri: string) { super(parent); this.commandId = 'azureStorage.editFile'; } @@ -51,11 +52,6 @@ export class FileTreeItem extends AzExtTreeItem implements ICopyUrl, ITransferSr return new vscode.ThemeIcon('file'); } - public get resourceUri(): string { - const shareClient = this.root.createShareServiceClient().getShareClient(this.shareName); - return shareClient.url; - } - public get transferSasToken(): string { const accountSASSignatureValues: AccountSASSignatureValues = { expiresOn: new Date(Date.now() + threeDaysInMS), @@ -67,7 +63,7 @@ export class FileTreeItem extends AzExtTreeItem implements ICopyUrl, ITransferSr } public async copyUrl(): Promise { - const fileClient: ShareFileClient = createFileClient(this.root, this.shareName, this.directoryPath, this.fileName); + const fileClient: ShareFileClient = await createFileClient(this.root, this.shareName, this.directoryPath, this.fileName); const url = fileClient.url; await copyAndShowToast(url, 'File URL'); } diff --git a/src/tree/queue/QueueGroupTreeItem.ts b/src/tree/queue/QueueGroupTreeItem.ts index ad75251b..9e650a97 100644 --- a/src/tree/queue/QueueGroupTreeItem.ts +++ b/src/tree/queue/QueueGroupTreeItem.ts @@ -76,7 +76,7 @@ export class QueueGroupTreeItem extends AzExtParentTreeItem implements IStorageT } async listQueues(continuationToken?: string): Promise { - const queueServiceClient = this.root.createQueueServiceClient(); + const queueServiceClient = await this.root.createQueueServiceClient(); const response: AsyncIterableIterator = queueServiceClient.listQueues().byPage({ continuationToken, maxPageSize }); // eslint-disable-next-line @typescript-eslint/no-unsafe-return @@ -110,7 +110,7 @@ export class QueueGroupTreeItem extends AzExtParentTreeItem implements IStorageT } private async createQueue(name: string): Promise { - const queueServiceClient = this.root.createQueueServiceClient(); + const queueServiceClient = await this.root.createQueueServiceClient(); await queueServiceClient.createQueue(name); const queuesResponse: ListQueuesSegmentResponse = await this.listQueues(); diff --git a/src/tree/queue/QueueTreeItem.ts b/src/tree/queue/QueueTreeItem.ts index 633aa9da..8e9e1ba6 100644 --- a/src/tree/queue/QueueTreeItem.ts +++ b/src/tree/queue/QueueTreeItem.ts @@ -36,7 +36,7 @@ export class QueueTreeItem extends AzExtTreeItem implements IStorageTreeItem { const message: string = `Are you sure you want to delete queue '${this.label}' and all its contents?`; const result = await context.ui.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel); if (result === DialogResponses.deleteResponse) { - const queueServiceClient = this.root.createQueueServiceClient(); + const queueServiceClient = await this.root.createQueueServiceClient(); await queueServiceClient.deleteQueue(this.queue.name); } else { throw new UserCancelledError(); diff --git a/src/tree/table/TableGroupTreeItem.ts b/src/tree/table/TableGroupTreeItem.ts index cfb593d1..ae5c9b6c 100644 --- a/src/tree/table/TableGroupTreeItem.ts +++ b/src/tree/table/TableGroupTreeItem.ts @@ -76,7 +76,7 @@ export class TableGroupTreeItem extends AzExtParentTreeItem implements IStorageT } async listTables(continuationToken?: string): Promise { - const tableServiceClient = this.root.createTableServiceClient(); + const tableServiceClient = await this.root.createTableServiceClient(); const response: AsyncIterableIterator = tableServiceClient.listTables().byPage({ continuationToken, maxPageSize }); // eslint-disable-next-line @typescript-eslint/no-unsafe-return @@ -110,7 +110,7 @@ export class TableGroupTreeItem extends AzExtParentTreeItem implements IStorageT } private async createTable(name: string): Promise { - const tableServiceClient = this.root.createTableServiceClient(); + const tableServiceClient = await this.root.createTableServiceClient(); await tableServiceClient.createTable(name); const tablesResponse = await this.listTables(); diff --git a/src/tree/table/TableTreeItem.ts b/src/tree/table/TableTreeItem.ts index 8b148c42..d9e6564a 100644 --- a/src/tree/table/TableTreeItem.ts +++ b/src/tree/table/TableTreeItem.ts @@ -34,7 +34,7 @@ export class TableTreeItem extends AzExtTreeItem implements IStorageTreeItem { const message: string = `Are you sure you want to delete table '${this.label}' and all its contents?`; const result = await context.ui.showWarningMessage(message, { modal: true }, DialogResponses.deleteResponse, DialogResponses.cancel); if (result === DialogResponses.deleteResponse) { - const tableServiceClient = this.root.createTableServiceClient(); + const tableServiceClient = await this.root.createTableServiceClient(); await tableServiceClient.deleteTable(this.tableName); } else { throw new UserCancelledError(); diff --git a/src/utils/blobUtils.ts b/src/utils/blobUtils.ts index 0f0e3ecc..a0bde321 100644 --- a/src/utils/blobUtils.ts +++ b/src/utils/blobUtils.ts @@ -17,24 +17,24 @@ import { BlobDirectoryTreeItem } from '../tree/blob/BlobDirectoryTreeItem'; import { BlobTreeItem } from '../tree/blob/BlobTreeItem'; import { localize } from './localize'; -export function createBlobContainerClient(root: IStorageRoot, containerName: string): ContainerClient { - const blobServiceClient: BlobServiceClient = root.createBlobServiceClient(); +export async function createBlobContainerClient(root: IStorageRoot, containerName: string): Promise { + const blobServiceClient: BlobServiceClient = await root.createBlobServiceClient(); return blobServiceClient.getContainerClient(containerName); } -export function createBlobClient(root: IStorageRoot, containerName: string, blobName: string): BlobClient { - const blobContainerClient: ContainerClient = createBlobContainerClient(root, containerName); +export async function createBlobClient(root: IStorageRoot, containerName: string, blobName: string): Promise { + const blobContainerClient: ContainerClient = await createBlobContainerClient(root, containerName); return blobContainerClient.getBlobClient(blobName); } -export function createBlockBlobClient(root: IStorageRoot, containerName: string, blobName: string): BlockBlobClient { - const blobContainerClient: ContainerClient = createBlobContainerClient(root, containerName); +export async function createBlockBlobClient(root: IStorageRoot, containerName: string, blobName: string): Promise { + const blobContainerClient: ContainerClient = await createBlobContainerClient(root, containerName); return blobContainerClient.getBlockBlobClient(blobName); } export async function loadMoreBlobChildren(parent: BlobContainerTreeItem | BlobDirectoryTreeItem, continuationToken?: string): Promise<{ children: AzExtTreeItem[], continuationToken?: string }> { const prefix: string | undefined = parent instanceof BlobDirectoryTreeItem ? parent.dirPath : undefined; - const containerClient: ContainerClient = createBlobContainerClient(parent.root, parent.container.name); + const containerClient: ContainerClient = await createBlobContainerClient(parent.root, parent.container.name); const settings: PageSettings = { continuationToken, // https://github.com/Azure/Azurite/issues/605 @@ -49,12 +49,14 @@ export async function loadMoreBlobChildren(parent: BlobContainerTreeItem | BlobD const children: AzExtTreeItem[] = []; for (const blob of responseValue.segment.blobItems) { // NOTE: `blob.name` as returned from Azure is actually the blob path in the container - children.push(new BlobTreeItem(parent, blob.name, parent.container)); + const innerContainerClient = await createBlobContainerClient(parent.root, blob.name); + children.push(new BlobTreeItem(parent, blob.name, parent.container, innerContainerClient.url)); } for (const directory of responseValue.segment.blobPrefixes || []) { // NOTE: `directory.name` as returned from Azure is actually the directory path in the container - children.push(new BlobDirectoryTreeItem(parent, directory.name, parent.container)); + const innerContainerClient = await createBlobContainerClient(parent.root, directory.name); + children.push(new BlobDirectoryTreeItem(parent, directory.name, parent.container, innerContainerClient.url)); } return { children, continuationToken }; @@ -68,14 +70,15 @@ export async function createChildAsNewBlockBlob(parent: BlobContainerTreeItem | context.showCreatingTreeItem(blobPath); progress.report({ message: `Azure Storage: Creating block blob '${blobPath}'` }); await createOrUpdateBlockBlob(parent, blobPath, context?.contents || ''); - return new BlobTreeItem(parent, blobPath, parent.container); + const client = await createBlobContainerClient(parent.root, parent.container.name); + return new BlobTreeItem(parent, blobPath, parent.container, client.url); }); } export async function createOrUpdateBlockBlob(parent: BlobContainerTreeItem | BlobDirectoryTreeItem, name: string, text?: string | Buffer): Promise { text = text ? text : ''; const contentLength: number = text instanceof Buffer ? text.byteLength : text.length; - const containerClient: ContainerClient = createBlobContainerClient(parent.root, parent.container.name); + const containerClient: ContainerClient = await createBlobContainerClient(parent.root, parent.container.name); let properties: BlockBlobUploadOptions | undefined = await getExistingProperties(parent, name); properties = properties || {}; @@ -86,7 +89,7 @@ export async function createOrUpdateBlockBlob(parent: BlobContainerTreeItem | Bl } export async function doesBlobExist(treeItem: BlobContainerTreeItem | BlobDirectoryTreeItem, blobPath: string): Promise { - const blobClient: BlobClient = createBlobClient(treeItem.root, treeItem.container.name, blobPath); + const blobClient: BlobClient = await createBlobClient(treeItem.root, treeItem.container.name, blobPath); return blobClient.exists(); } @@ -96,7 +99,7 @@ export async function doesBlobDirectoryExist(treeItem: BlobContainerTreeItem | B blobDirectoryName = `${blobDirectoryName}${sep}`; } - const containerClient: ContainerClient = createBlobContainerClient(treeItem.root, treeItem.container.name); + const containerClient: ContainerClient = await createBlobContainerClient(treeItem.root, treeItem.container.name); const response: AsyncIterableIterator = containerClient.listBlobsByHierarchy(sep, { prefix: blobDirectoryName }).byPage({ maxPageSize: 1 }); for await (const responseValue of response) { @@ -109,7 +112,7 @@ export async function doesBlobDirectoryExist(treeItem: BlobContainerTreeItem | B } export async function getExistingProperties(parent: BlobTreeItem | BlobContainerTreeItem | BlobDirectoryTreeItem, blobPath: string): Promise { - const blockBlobClient: BlockBlobClient = createBlockBlobClient(parent.root, parent.container.name, blobPath); + const blockBlobClient: BlockBlobClient = await createBlockBlobClient(parent.root, parent.container.name, blobPath); if (await blockBlobClient.exists()) { const existingProperties: BlobGetPropertiesResponse = await blockBlobClient.getProperties(); return { diff --git a/src/utils/directoryUtils.ts b/src/utils/directoryUtils.ts index 586731be..6c78856b 100644 --- a/src/utils/directoryUtils.ts +++ b/src/utils/directoryUtils.ts @@ -21,14 +21,14 @@ export async function askAndCreateChildDirectory(parent: FileShareTreeItem | Dir return await window.withProgress({ location: ProgressLocation.Window }, async (progress) => { context.showCreatingTreeItem(dirName); progress.report({ message: `Azure Storage: Creating directory '${path.posix.join(parentPath, dirName)}'` }); - const directoryClient: ShareDirectoryClient = createDirectoryClient(parent.root, shareName, path.posix.join(parentPath, dirName)); + const directoryClient: ShareDirectoryClient = await createDirectoryClient(parent.root, shareName, path.posix.join(parentPath, dirName)); await directoryClient.create(); - return new DirectoryTreeItem(parent, parentPath, dirName, shareName); + return new DirectoryTreeItem(parent, parentPath, dirName, shareName, directoryClient.url); }); } export async function listFilesInDirectory(directory: string, shareName: string, root: IStorageRoot, currentToken?: string): Promise<{ files: FileItem[], directories: DirectoryItem[], continuationToken: string }> { - const directoryClient: ShareDirectoryClient = createDirectoryClient(root, shareName, directory); + const directoryClient: ShareDirectoryClient = await createDirectoryClient(root, shareName, directory); const response: AsyncIterableIterator = directoryClient.listFilesAndDirectories().byPage({ continuationToken: currentToken, maxPageSize }); // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment @@ -71,13 +71,13 @@ export async function deleteDirectoryAndContents(directory: string, shareName: s } } - const directoryClient: ShareDirectoryClient = createDirectoryClient(root, shareName, directory); + const directoryClient: ShareDirectoryClient = await createDirectoryClient(root, shareName, directory); await directoryClient.delete(); ext.outputChannel.appendLog(`Deleted directory "${directory}"`); } export async function doesDirectoryExist(parent: FileShareTreeItem | DirectoryTreeItem, directoryPath: string, shareName: string): Promise { - const directoryClient: ShareDirectoryClient = createDirectoryClient(parent.root, shareName, directoryPath); + const directoryClient: ShareDirectoryClient = await createDirectoryClient(parent.root, shareName, directoryPath); try { await directoryClient.getProperties(); return true; diff --git a/src/utils/fileUtils.ts b/src/utils/fileUtils.ts index 6ef5c503..91debb75 100644 --- a/src/utils/fileUtils.ts +++ b/src/utils/fileUtils.ts @@ -17,18 +17,18 @@ import { doesDirectoryExist } from './directoryUtils'; import { localize } from './localize'; import { validateFileOrDirectoryName } from './validateNames'; -export function createShareClient(root: IStorageRoot, shareName: string): ShareClient { - const shareServiceClient: ShareServiceClient = root.createShareServiceClient(); +export async function createShareClient(root: IStorageRoot, shareName: string): Promise { + const shareServiceClient: ShareServiceClient = await root.createShareServiceClient(); return shareServiceClient.getShareClient(shareName); } -export function createDirectoryClient(root: IStorageRoot, shareName: string, directoryName: string): ShareDirectoryClient { - const shareClient: ShareClient = createShareClient(root, shareName); +export async function createDirectoryClient(root: IStorageRoot, shareName: string, directoryName: string): Promise { + const shareClient: ShareClient = await createShareClient(root, shareName); return shareClient.getDirectoryClient(directoryName); } -export function createFileClient(root: IStorageRoot, shareName: string, directoryName: string, fileName: string): ShareFileClient { - const directoryClient: ShareDirectoryClient = createDirectoryClient(root, shareName, directoryName); +export async function createFileClient(root: IStorageRoot, shareName: string, directoryName: string, fileName: string): Promise { + const directoryClient: ShareDirectoryClient = await createDirectoryClient(root, shareName, directoryName); return directoryClient.getFileClient(fileName); } @@ -38,7 +38,8 @@ export async function askAndCreateEmptyTextFile(parent: FileShareTreeItem | Dire context.showCreatingTreeItem(fileName); progress.report({ message: `Azure Storage: Creating file '${fileName}'` }); await createFile(directoryPath, fileName, shareName, parent.root); - return new FileTreeItem(parent, fileName, directoryPath, shareName); + const fileClient: ShareFileClient = await createFileClient(parent.root, shareName, directoryPath, fileName); + return new FileTreeItem(parent, fileName, directoryPath, shareName, fileClient.url); }); } @@ -59,7 +60,7 @@ export async function getFileOrDirectoryName(context: IActionContext, parent: Fi } export async function doesFileExist(fileName: string, parent: FileShareTreeItem | DirectoryTreeItem, directoryPath: string, shareName: string): Promise { - const fileService: ShareFileClient = createFileClient(parent.root, shareName, directoryPath, fileName); + const fileService: ShareFileClient = await createFileClient(parent.root, shareName, directoryPath, fileName); try { await fileService.getProperties(); return true; @@ -69,7 +70,7 @@ export async function doesFileExist(fileName: string, parent: FileShareTreeItem } export async function createFile(directoryPath: string, name: string, shareName: string, root: IStorageRoot, options?: FileCreateOptions): Promise { - const fileClient: ShareFileClient = createFileClient(root, shareName, directoryPath, name); + const fileClient: ShareFileClient = await createFileClient(root, shareName, directoryPath, name); options = options || {}; options.fileHttpHeaders = options.fileHttpHeaders || {}; @@ -79,7 +80,7 @@ export async function createFile(directoryPath: string, name: string, shareName: } export async function updateFileFromText(directoryPath: string, name: string, shareName: string, root: IStorageRoot, text: string | Buffer): Promise { - const fileClient: ShareFileClient = createFileClient(root, shareName, directoryPath, name); + const fileClient: ShareFileClient = await createFileClient(root, shareName, directoryPath, name); let options: FileParallelUploadOptions = await getExistingCreateOptions(directoryPath, name, shareName, root); options = options || {}; options.fileHttpHeaders = options.fileHttpHeaders || {}; @@ -88,13 +89,13 @@ export async function updateFileFromText(directoryPath: string, name: string, sh } export async function deleteFile(directory: string, name: string, share: string, root: IStorageRoot): Promise { - const fileClient = createFileClient(root, share, directory, name); + const fileClient = await createFileClient(root, share, directory, name); await fileClient.delete(); } // Gets existing create options using the `@azure/storage-file-share` SDK export async function getExistingCreateOptions(directoryPath: string, name: string, shareName: string, root: IStorageRoot): Promise { - const fileClient: ShareFileClient = createFileClient(root, shareName, directoryPath, name); + const fileClient: ShareFileClient = await createFileClient(root, shareName, directoryPath, name); const propertiesResult: FileGetPropertiesResponse = await fileClient.getProperties(); const options: FileCreateOptions = {}; options.fileHttpHeaders = {}; diff --git a/test/nightly/storageAccountActions.test.ts b/test/nightly/storageAccountActions.test.ts index 4424a10d..82f6756b 100644 --- a/test/nightly/storageAccountActions.test.ts +++ b/test/nightly/storageAccountActions.test.ts @@ -13,7 +13,7 @@ import * as assert from 'assert'; import * as vscode from 'vscode'; import { copyConnectionString, copyPrimaryKey, createBlobContainer, createFileShare, createQueue, createStorageAccount, createStorageAccountAdvanced, createTable, deleteBlobContainer, deleteFileShare, deleteQueue, deleteStorageAccount, deleteTable, DialogResponses, getRandomHexString } from '../../extension.bundle'; import { longRunningTestsEnabled } from '../global.test'; -import { resourceGroupsToDelete, webSiteClient as client } from './global.resource.test'; +import { webSiteClient as client, resourceGroupsToDelete } from './global.resource.test'; // eslint-disable-next-line @typescript-eslint/no-misused-promises suite('Storage Account Actions', function (this: Mocha.Suite): void {