diff --git a/package.json b/package.json index 6a52cb98..7ed868aa 100644 --- a/package.json +++ b/package.json @@ -821,7 +821,7 @@ "@azure/storage-file-datalake": "^12.1.1", "@azure/storage-file-share": "^12.25.0-beta.1", "@azure/storage-queue": "^12.7.0", - "@microsoft/vscode-azext-azureutils": "^4.0.1", + "@microsoft/vscode-azext-azureutils": "^4.3.0", "@microsoft/vscode-azext-utils": "^4.0.4", "@microsoft/vscode-azureresources-api": "^3.1.0", "mime": "^2.4.4", diff --git a/src/BlobContainerFS.ts b/src/BlobContainerFS.ts index f591d3e5..66aad25b 100644 --- a/src/BlobContainerFS.ts +++ b/src/BlobContainerFS.ts @@ -23,6 +23,7 @@ import { createStorageClient } from './utils/azureClients'; import { BlobPathUtils } from './utils/blobPathUtils'; import { createBlockBlobClient } from './utils/blobUtils'; import { localize } from './utils/localize'; +import { withProxyOptions } from './utils/proxyUtils'; import { nonNullValue } from "./utils/nonNull"; import { StorageAccountKeyWrapper } from './utils/storageWrappers'; @@ -136,7 +137,7 @@ export class BlobContainerFS implements vscode.FileSystemProvider { throw Error("Unable to get key credential."); } - const serviceClient = new BlobServiceClient(blobEndpoint, credential); + const serviceClient = new BlobServiceClient(blobEndpoint, credential, withProxyOptions(blobEndpoint)); const containerClient = serviceClient.getContainerClient(containerName); const blobClient = containerClient.getBlobClient(blobPath); @@ -160,12 +161,12 @@ export class BlobContainerFS implements vscode.FileSystemProvider { throw Error("Unable to get key credential."); } - const serviceClient = new DataLakeServiceClient(dfsEndpoint, credential); + const serviceClient = new DataLakeServiceClient(dfsEndpoint, credential, withProxyOptions(dfsEndpoint)); const fileSystemClient = serviceClient.getFileSystemClient(containerName); const pathUrl = new URL(dfsEndpoint); pathUrl.pathname = `${containerName}/${blobPath}`; - const pathClient = new DataLakePathClient(pathUrl.toString(), credential); + const pathClient = new DataLakePathClient(pathUrl.toString(), credential, withProxyOptions(dfsEndpoint)); return { fileSystemClient, pathClient }; } diff --git a/src/tree/StorageAccountTreeItem.ts b/src/tree/StorageAccountTreeItem.ts index 40585a5b..c40ae387 100644 --- a/src/tree/StorageAccountTreeItem.ts +++ b/src/tree/StorageAccountTreeItem.ts @@ -25,6 +25,7 @@ import { createActivityContext } from '../utils/activityUtils'; import { createStorageClient } from '../utils/azureClients'; import { localize } from '../utils/localize'; import { nonNullProp } from '../utils/nonNull'; +import { withProxyOptions } from '../utils/proxyUtils'; import { StorageAccountKeyWrapper, StorageAccountWrapper } from '../utils/storageWrappers'; import { IStorageRoot } from './IStorageRoot'; import { IStorageTreeItem } from './IStorageTreeItem'; @@ -289,7 +290,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT if (this.allowSharedKeyAccess) { try { const credential = new StorageSharedKeyCredentialBlob(this.storageAccount.name, ((await this.getKey()).value)); - client = new BlobServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'), credential); + client = new BlobServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'), credential, withProxyOptions(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'))); await client.getProperties(); // Trigger a request to validate the key } catch { // ignore and try scoped token @@ -298,7 +299,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT if (!client) { const token = await this._subscription.createCredentialsForScopes(['https://storage.azure.com/.default']); - client = new BlobServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'), token); + client = new BlobServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'), token, withProxyOptions(nonNullProp(this.storageAccount.primaryEndpoints, 'blob'))); await client.getProperties(); // Trigger a request to validate the token } @@ -309,7 +310,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT if (this.allowSharedKeyAccess) { try { const credential = new StorageSharedKeyCredentialFileShare(this.storageAccount.name, ((await this.getKey()).value)); - client = new ShareServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), credential); + client = new ShareServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), credential, withProxyOptions(nonNullProp(this.storageAccount.primaryEndpoints, 'file'))); await client.getProperties(); // Trigger a request to validate the key } catch { // ignore and try scoped token @@ -317,7 +318,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT } if (!client) { const token = await this._subscription.createCredentialsForScopes(['https://storage.azure.com/.default']); - client = new ShareServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), token, { fileRequestIntent: 'backup' }); + client = new ShareServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), token, withProxyOptions(nonNullProp(this.storageAccount.primaryEndpoints, 'file'), { fileRequestIntent: 'backup' })); await client.getProperties(); // Trigger a request to validate the token } @@ -328,7 +329,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT if (this.allowSharedKeyAccess) { try { const credential = new StorageSharedKeyCredentialQueue(this.storageAccount.name, ((await this.getKey()).value)); - client = new QueueServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'), credential); + client = new QueueServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'), credential, withProxyOptions(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'))); await client.getProperties(); // Trigger a request to validate the key } catch { // ignore and try scoped token @@ -336,7 +337,7 @@ export class StorageAccountTreeItem implements ResolvedStorageAccount, IStorageT } if (!client) { const token = await this._subscription.createCredentialsForScopes(['https://storage.azure.com/.default']); - client = new QueueServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'), token); + client = new QueueServiceClient(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'), token, withProxyOptions(nonNullProp(this.storageAccount.primaryEndpoints, 'queue'))); await client.getProperties(); // Trigger a request to validate the token } diff --git a/src/utils/proxyUtils.ts b/src/utils/proxyUtils.ts new file mode 100644 index 00000000..3e6c5305 --- /dev/null +++ b/src/utils/proxyUtils.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getProxySettings, type ProxySettings } from '@microsoft/vscode-azext-azureutils'; + +/** + * Merges VS Code's proxy configuration (`http.proxy` / `http.noProxy`, with the standard proxy + * environment variables as a fallback) into the pipeline options used to construct a storage + * data-plane service client, so requests route through a corporate proxy. + * + * The storage SDKs (`@azure/storage-blob`, `-file-share`, `-queue`) do not consult VS Code's + * `http.*` settings on their own, so operations fail on networks that require an explicit proxy. + * Returns `options` unchanged when no proxy applies to `endpointUrl` (for example when the host is + * matched by `http.noProxy`/`NO_PROXY`, or proxy support is turned off). + */ +export function withProxyOptions(endpointUrl: string): { proxyOptions?: ProxySettings } | undefined; +export function withProxyOptions(endpointUrl: string, options: T): (T & { proxyOptions?: ProxySettings }) | undefined; +export function withProxyOptions(endpointUrl: string, options?: T): (T & { proxyOptions?: ProxySettings }) | { proxyOptions?: ProxySettings } | undefined { + const proxyOptions = getProxySettings(endpointUrl); + if (!proxyOptions) { + return options; + } + return options ? { ...options, proxyOptions } : { proxyOptions }; +}