mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Change HTTP request to axios (#24549)
* change request to axios * fix typo * remove httpClient * fix build * change azurenetworkresponse to axiosresponse
This commit is contained in:
@@ -22,8 +22,7 @@ import { MemoryDatabase } from '../utils/memoryDatabase';
|
|||||||
import { Logger } from '../../utils/Logger';
|
import { Logger } from '../../utils/Logger';
|
||||||
import { AzureAuthError } from './azureAuthError';
|
import { AzureAuthError } from './azureAuthError';
|
||||||
import { AccountInfo, AuthError, AuthenticationResult, InteractionRequiredAuthError, PublicClientApplication } from '@azure/msal-node';
|
import { AccountInfo, AuthError, AuthenticationResult, InteractionRequiredAuthError, PublicClientApplication } from '@azure/msal-node';
|
||||||
import { HttpClient } from './httpClient';
|
import { getTenantIgnoreList, updateTenantIgnoreList } from '../../utils';
|
||||||
import { getProxyEnabledHttpClient, getTenantIgnoreList, updateTenantIgnoreList } from '../../utils';
|
|
||||||
import { errorToPromptFailedResult } from './networkUtils';
|
import { errorToPromptFailedResult } from './networkUtils';
|
||||||
import { MsalCachePluginProvider } from '../utils/msalCachePlugin';
|
import { MsalCachePluginProvider } from '../utils/msalCachePlugin';
|
||||||
import { isErrorResponseBodyWithError } from '../../azureResource/utils';
|
import { isErrorResponseBodyWithError } from '../../azureResource/utils';
|
||||||
@@ -45,7 +44,6 @@ export abstract class AzureAuth implements vscode.Disposable {
|
|||||||
protected readonly scopesString: string;
|
protected readonly scopesString: string;
|
||||||
protected readonly clientId: string;
|
protected readonly clientId: string;
|
||||||
protected readonly resources: Resource[];
|
protected readonly resources: Resource[];
|
||||||
protected readonly httpClient: HttpClient;
|
|
||||||
private readonly _disposableStore: vscode.Disposable[];
|
private readonly _disposableStore: vscode.Disposable[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -100,7 +98,6 @@ export abstract class AzureAuth implements vscode.Disposable {
|
|||||||
|
|
||||||
this.scopes = [...this.metadata.settings.scopes];
|
this.scopes = [...this.metadata.settings.scopes];
|
||||||
this.scopesString = this.scopes.join(' ');
|
this.scopesString = this.scopes.join(' ');
|
||||||
this.httpClient = getProxyEnabledHttpClient();
|
|
||||||
this._disposableStore.push(this.uriEventEmitter);
|
this._disposableStore.push(this.uriEventEmitter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,413 +0,0 @@
|
|||||||
/*---------------------------------------------------------------------------------------------
|
|
||||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
|
||||||
*--------------------------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
import { AzureNetworkResponse } from 'azurecore';
|
|
||||||
import * as http from 'http';
|
|
||||||
import * as https from 'https';
|
|
||||||
import { TextEncoder } from 'util';
|
|
||||||
import { NetworkRequestOptions, urlToHttpOptions } from './networkUtils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* http methods
|
|
||||||
*/
|
|
||||||
export enum HttpMethod {
|
|
||||||
GET = 'get',
|
|
||||||
POST = 'post',
|
|
||||||
PUT = 'put',
|
|
||||||
DELETE = 'delete',
|
|
||||||
PATCH = 'patch'
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum HttpStatus {
|
|
||||||
SUCCESS_RANGE_START = 200,
|
|
||||||
SUCCESS_RANGE_END = 299,
|
|
||||||
REDIRECT = 302,
|
|
||||||
CLIENT_ERROR_RANGE_START = 400,
|
|
||||||
CLIENT_ERROR_RANGE_END = 499,
|
|
||||||
SERVER_ERROR_RANGE_START = 500,
|
|
||||||
SERVER_ERROR_RANGE_END = 599
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum ProxyStatus {
|
|
||||||
SUCCESS_RANGE_START = 200,
|
|
||||||
SUCCESS_RANGE_END = 299,
|
|
||||||
SERVER_ERROR = 500
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class implements the API for network requests.
|
|
||||||
*/
|
|
||||||
export class HttpClient {
|
|
||||||
private proxyUrl: string;
|
|
||||||
private customAgentOptions: http.AgentOptions | https.AgentOptions;
|
|
||||||
static readonly AUTHORIZATION_PENDING: string = 'authorization_pending';
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
proxyUrl?: string,
|
|
||||||
customAgentOptions?: http.AgentOptions | https.AgentOptions
|
|
||||||
) {
|
|
||||||
this.proxyUrl = proxyUrl || '';
|
|
||||||
this.customAgentOptions = customAgentOptions || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Http Get request
|
|
||||||
* @param url
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
async sendGetRequestAsync<T>(
|
|
||||||
url: string,
|
|
||||||
options?: NetworkRequestOptions,
|
|
||||||
cancellationToken?: number | undefined
|
|
||||||
): Promise<AzureNetworkResponse<T>> {
|
|
||||||
if (this.proxyUrl) {
|
|
||||||
return networkRequestViaProxy(url, this.proxyUrl, HttpMethod.GET, options, this.customAgentOptions as http.AgentOptions, cancellationToken);
|
|
||||||
} else {
|
|
||||||
return networkRequestViaHttps(url, HttpMethod.GET, options, this.customAgentOptions as https.AgentOptions, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Http Post request
|
|
||||||
* @param url
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
async sendPostRequestAsync<T>(
|
|
||||||
url: string,
|
|
||||||
options?: NetworkRequestOptions,
|
|
||||||
cancellationToken?: number
|
|
||||||
): Promise<AzureNetworkResponse<T>> {
|
|
||||||
if (this.proxyUrl) {
|
|
||||||
return networkRequestViaProxy(url, this.proxyUrl, HttpMethod.POST, options, this.customAgentOptions as http.AgentOptions, cancellationToken);
|
|
||||||
} else {
|
|
||||||
return networkRequestViaHttps(url, HttpMethod.POST, options, this.customAgentOptions as https.AgentOptions, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Http Put request
|
|
||||||
* @param url
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
async sendPutRequestAsync<T>(
|
|
||||||
url: string,
|
|
||||||
options?: NetworkRequestOptions,
|
|
||||||
cancellationToken?: number
|
|
||||||
): Promise<AzureNetworkResponse<T>> {
|
|
||||||
if (this.proxyUrl) {
|
|
||||||
return networkRequestViaProxy(url, this.proxyUrl, HttpMethod.PUT, options, this.customAgentOptions as http.AgentOptions, cancellationToken);
|
|
||||||
} else {
|
|
||||||
return networkRequestViaHttps(url, HttpMethod.PUT, options, this.customAgentOptions as https.AgentOptions, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Http Delete request
|
|
||||||
* @param url
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
async sendDeleteRequestAsync<T>(
|
|
||||||
url: string,
|
|
||||||
options?: NetworkRequestOptions
|
|
||||||
): Promise<AzureNetworkResponse<T>> {
|
|
||||||
if (this.proxyUrl) {
|
|
||||||
return networkRequestViaProxy(url, this.proxyUrl, HttpMethod.DELETE, options, this.customAgentOptions as http.AgentOptions);
|
|
||||||
} else {
|
|
||||||
return networkRequestViaHttps(url, HttpMethod.DELETE, options, this.customAgentOptions as https.AgentOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Http Patch request
|
|
||||||
* @param url
|
|
||||||
* @param options
|
|
||||||
*/
|
|
||||||
async sendPatchRequestAsync<T>(
|
|
||||||
url: string,
|
|
||||||
options?: NetworkRequestOptions,
|
|
||||||
cancellationToken?: number
|
|
||||||
): Promise<AzureNetworkResponse<T>> {
|
|
||||||
if (this.proxyUrl) {
|
|
||||||
return networkRequestViaProxy(url, this.proxyUrl, HttpMethod.PATCH, options, this.customAgentOptions as http.AgentOptions, cancellationToken);
|
|
||||||
} else {
|
|
||||||
return networkRequestViaHttps(url, HttpMethod.PATCH, options, this.customAgentOptions as https.AgentOptions, cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const networkRequestViaProxy = <T>(
|
|
||||||
destinationUrlString: string,
|
|
||||||
proxyUrlString: string,
|
|
||||||
httpMethod: string,
|
|
||||||
options?: NetworkRequestOptions,
|
|
||||||
agentOptions?: http.AgentOptions,
|
|
||||||
timeout?: number
|
|
||||||
): Promise<AzureNetworkResponse<T>> => {
|
|
||||||
const destinationUrl = new URL(destinationUrlString);
|
|
||||||
const proxyUrl = new URL(proxyUrlString);
|
|
||||||
|
|
||||||
// 'method: connect' must be used to establish a connection to the proxy
|
|
||||||
const headers = options?.headers || {} as Record<string, string>;
|
|
||||||
const tunnelRequestOptions: https.RequestOptions = {
|
|
||||||
host: proxyUrl.hostname,
|
|
||||||
port: proxyUrl.port,
|
|
||||||
method: 'CONNECT',
|
|
||||||
path: destinationUrl.hostname,
|
|
||||||
headers: headers
|
|
||||||
};
|
|
||||||
|
|
||||||
if (timeout) {
|
|
||||||
tunnelRequestOptions.timeout = timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (agentOptions && Object.keys(agentOptions).length) {
|
|
||||||
tunnelRequestOptions.agent = new http.Agent(agentOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// compose a request string for the socket
|
|
||||||
let postRequestStringContent: string = '';
|
|
||||||
if (httpMethod === HttpMethod.POST || httpMethod === HttpMethod.PUT) {
|
|
||||||
// Note: Text Encoder is necessary here because otherwise it was not able to handle Chinese characters in table names.
|
|
||||||
const body = (new TextEncoder()).encode(JSON.stringify(options?.body || ''));
|
|
||||||
postRequestStringContent =
|
|
||||||
'Content-Type: application/json\r\n' +
|
|
||||||
`Content-Length: ${body.length}\r\n` +
|
|
||||||
`\r\n${body}`;
|
|
||||||
}
|
|
||||||
const outgoingRequestString = `${httpMethod.toUpperCase()} ${destinationUrl.href} HTTP/1.1\r\n` +
|
|
||||||
`Host: ${destinationUrl.host}\r\n` +
|
|
||||||
'Connection: close\r\n' +
|
|
||||||
postRequestStringContent +
|
|
||||||
'\r\n';
|
|
||||||
|
|
||||||
return new Promise<AzureNetworkResponse<T>>(((resolve, reject) => {
|
|
||||||
const request = http.request(tunnelRequestOptions);
|
|
||||||
|
|
||||||
if (tunnelRequestOptions.timeout) {
|
|
||||||
request.on('timeout', () => {
|
|
||||||
request.destroy();
|
|
||||||
reject(new Error('Request time out'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
request.end();
|
|
||||||
|
|
||||||
// establish connection to the proxy
|
|
||||||
request.on('connect', (response, socket) => {
|
|
||||||
const proxyStatusCode = response?.statusCode || ProxyStatus.SERVER_ERROR;
|
|
||||||
if ((proxyStatusCode < ProxyStatus.SUCCESS_RANGE_START) || (proxyStatusCode > ProxyStatus.SUCCESS_RANGE_END)) {
|
|
||||||
request.destroy();
|
|
||||||
socket.destroy();
|
|
||||||
reject(new Error(`Error connecting to proxy. Http status code: ${response.statusCode}. Http status message: ${response?.statusMessage || 'Unknown'}`));
|
|
||||||
}
|
|
||||||
if (tunnelRequestOptions.timeout) {
|
|
||||||
socket.setTimeout(tunnelRequestOptions.timeout);
|
|
||||||
socket.on('timeout', () => {
|
|
||||||
request.destroy();
|
|
||||||
socket.destroy();
|
|
||||||
reject(new Error('Request time out'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// make a request over an HTTP tunnel
|
|
||||||
socket.write(outgoingRequestString);
|
|
||||||
|
|
||||||
const data: Buffer[] = [];
|
|
||||||
socket.on('data', (chunk) => {
|
|
||||||
data.push(chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('end', () => {
|
|
||||||
// combine all received buffer streams into one buffer, and then into a string
|
|
||||||
const dataString = Buffer.concat([...data]).toString();
|
|
||||||
|
|
||||||
// separate each line into it's own entry in an arry
|
|
||||||
const dataStringArray = dataString.split('\r\n');
|
|
||||||
// the first entry will contain the statusCode and statusMessage
|
|
||||||
const httpStatusCode = parseInt(dataStringArray[0].split(' ')[1], undefined);
|
|
||||||
// remove 'HTTP/1.1' and the status code to get the status message
|
|
||||||
const statusMessage = dataStringArray[0].split(' ').slice(2).join(' ');
|
|
||||||
// the last entry will contain the body
|
|
||||||
const body = dataStringArray[dataStringArray.length - 1];
|
|
||||||
|
|
||||||
// everything in between the first and last entries are the headers
|
|
||||||
const headersArray = dataStringArray.slice(1, dataStringArray.length - 2);
|
|
||||||
|
|
||||||
// build an object out of all the headers
|
|
||||||
const entries = new Map();
|
|
||||||
headersArray.forEach((header) => {
|
|
||||||
/**
|
|
||||||
* the header might look like 'Content-Length: 1531', but that is just a string
|
|
||||||
* it needs to be converted to a key/value pair
|
|
||||||
* split the string at the first instance of ':'
|
|
||||||
* there may be more than one ':' if the value of the header is supposed to be a JSON object
|
|
||||||
*/
|
|
||||||
const headerKeyValue = header.split(new RegExp(/:\s(.*)/s));
|
|
||||||
entries.set(headerKeyValue[0], headerKeyValue[1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
const parsedHeaders = Object.fromEntries(entries) as Record<string, string>;
|
|
||||||
const networkResponse: AzureNetworkResponse<T> = {
|
|
||||||
headers: parsedHeaders,
|
|
||||||
data: parseBody(httpStatusCode, statusMessage, parsedHeaders, body) as T,
|
|
||||||
status: httpStatusCode
|
|
||||||
};
|
|
||||||
|
|
||||||
if (((httpStatusCode < HttpStatus.SUCCESS_RANGE_START) || (httpStatusCode > HttpStatus.SUCCESS_RANGE_END)) &&
|
|
||||||
// do not destroy the request for the device code flow
|
|
||||||
// @ts-ignore
|
|
||||||
networkResponse.data['error'] !== HttpClient.AUTHORIZATION_PENDING) {
|
|
||||||
request.destroy();
|
|
||||||
}
|
|
||||||
resolve(networkResponse);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('error', (chunk) => {
|
|
||||||
request.destroy();
|
|
||||||
socket.destroy();
|
|
||||||
reject(new Error(chunk.toString()));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
request.on('error', (chunk) => {
|
|
||||||
request.destroy();
|
|
||||||
reject(new Error(chunk.toString()));
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const networkRequestViaHttps = <T>(
|
|
||||||
urlString: string,
|
|
||||||
httpMethod: string,
|
|
||||||
options?: NetworkRequestOptions,
|
|
||||||
agentOptions?: https.AgentOptions,
|
|
||||||
timeout?: number
|
|
||||||
): Promise<AzureNetworkResponse<T>> => {
|
|
||||||
const isPostRequest = httpMethod === HttpMethod.POST;
|
|
||||||
const isPutRequest = httpMethod === HttpMethod.PUT;
|
|
||||||
const isPatchRequest = httpMethod === HttpMethod.PATCH;
|
|
||||||
// Note: Text Encoder is necessary here because otherwise it was not able to handle Chinese characters in table names.
|
|
||||||
const body = (new TextEncoder()).encode(options?.body || '');
|
|
||||||
const url = new URL(urlString);
|
|
||||||
const optionHeaders = options?.headers || {} as Record<string, string>;
|
|
||||||
let customOptions: https.RequestOptions = {
|
|
||||||
method: httpMethod,
|
|
||||||
headers: optionHeaders,
|
|
||||||
...urlToHttpOptions(url)
|
|
||||||
};
|
|
||||||
|
|
||||||
if (timeout) {
|
|
||||||
customOptions.timeout = timeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (agentOptions && Object.keys(agentOptions).length) {
|
|
||||||
customOptions.agent = new https.Agent(agentOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPostRequest || isPutRequest || isPatchRequest) {
|
|
||||||
// needed for post request to work
|
|
||||||
customOptions.headers = {
|
|
||||||
...customOptions.headers,
|
|
||||||
'Content-Length': body.length
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<AzureNetworkResponse<T>>((resolve, reject) => {
|
|
||||||
const request = https.request(customOptions);
|
|
||||||
|
|
||||||
if (timeout) {
|
|
||||||
request.on('timeout', () => {
|
|
||||||
request.destroy();
|
|
||||||
reject(new Error('Request timed out'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPostRequest || isPutRequest || isPatchRequest) {
|
|
||||||
request.write(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
request.end();
|
|
||||||
|
|
||||||
request.on('response', (response) => {
|
|
||||||
const headers = response.headers;
|
|
||||||
const statusCode = response.statusCode as number;
|
|
||||||
const statusMessage = response.statusMessage;
|
|
||||||
|
|
||||||
const data: Buffer[] = [];
|
|
||||||
response.on('data', (chunk) => {
|
|
||||||
data.push(chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
response.on('end', () => {
|
|
||||||
// combine all received buffer streams into one buffer, and then into a string
|
|
||||||
const dataBody = Buffer.concat([...data]).toString();
|
|
||||||
|
|
||||||
const parsedHeaders = headers as Record<string, string>;
|
|
||||||
const networkResponse: AzureNetworkResponse<T> = {
|
|
||||||
headers: parsedHeaders,
|
|
||||||
data: parseBody(statusCode, statusMessage, parsedHeaders, dataBody) as T,
|
|
||||||
status: statusCode
|
|
||||||
};
|
|
||||||
|
|
||||||
if (((statusCode < HttpStatus.SUCCESS_RANGE_START) || (statusCode > HttpStatus.SUCCESS_RANGE_END)) &&
|
|
||||||
// do not destroy the request for the device code flow
|
|
||||||
// @ts-ignore
|
|
||||||
networkResponse.data['error'] !== HttpClient.AUTHORIZATION_PENDING) {
|
|
||||||
request.destroy();
|
|
||||||
}
|
|
||||||
resolve(networkResponse);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
request.on('error', (chunk) => {
|
|
||||||
request.destroy();
|
|
||||||
reject(new Error(chunk.toString()));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if extra parsing is needed on the response from the server
|
|
||||||
* @param statusCode {number} the status code of the response from the server
|
|
||||||
* @param statusMessage {string | undefined} the status message of the response from the server
|
|
||||||
* @param headers {Record<string, string>} the headers of the response from the server
|
|
||||||
* @param body {string} the body from the response of the server
|
|
||||||
* @returns JSON parsed body or error object
|
|
||||||
*/
|
|
||||||
const parseBody = (statusCode: number, statusMessage: string | undefined, headers: Record<string, string>, body: string) => {
|
|
||||||
/*
|
|
||||||
* Informational responses (100 - 199)
|
|
||||||
* Successful responses (200 - 299)
|
|
||||||
* Redirection messages (300 - 399)
|
|
||||||
* Client error responses (400 - 499)
|
|
||||||
* Server error responses (500 - 599)
|
|
||||||
*/
|
|
||||||
|
|
||||||
let parsedBody: unknown;
|
|
||||||
try {
|
|
||||||
parsedBody = JSON.parse(body);
|
|
||||||
} catch (error) {
|
|
||||||
let errorType;
|
|
||||||
let errorDescriptionHelper;
|
|
||||||
if ((statusCode >= HttpStatus.CLIENT_ERROR_RANGE_START) && (statusCode <= HttpStatus.CLIENT_ERROR_RANGE_END)) {
|
|
||||||
errorType = 'client_error';
|
|
||||||
errorDescriptionHelper = 'A client';
|
|
||||||
} else if ((statusCode >= HttpStatus.SERVER_ERROR_RANGE_START) && (statusCode <= HttpStatus.SERVER_ERROR_RANGE_END)) {
|
|
||||||
errorType = 'server_error';
|
|
||||||
errorDescriptionHelper = 'A server';
|
|
||||||
} else {
|
|
||||||
errorType = 'unknown_error';
|
|
||||||
errorDescriptionHelper = 'An unknown';
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedBody = {
|
|
||||||
error: errorType,
|
|
||||||
error_description: `${errorDescriptionHelper} error occurred.\nHttp status code: ${statusCode}\nHttp status message: ${statusMessage || 'Unknown'}\nHeaders: ${JSON.stringify(headers)}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedBody;
|
|
||||||
};
|
|
||||||
@@ -9,7 +9,7 @@ import * as nls from 'vscode-nls';
|
|||||||
import * as Constants from '../constants';
|
import * as Constants from '../constants';
|
||||||
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
|
import { ResourceGraphClient } from '@azure/arm-resourcegraph';
|
||||||
import { TokenCredentials } from '@azure/ms-rest-js';
|
import { TokenCredentials } from '@azure/ms-rest-js';
|
||||||
import { AzureRestResponse, GetResourceGroupsResult, GetSubscriptionsResult, ResourceQueryResult, GetBlobContainersResult, GetFileSharesResult, HttpRequestMethod, GetLocationsResult, GetManagedDatabasesResult, CreateResourceGroupResult, GetBlobsResult, GetStorageAccountAccessKeyResult, AzureAccount, azureResource, AzureAccountProviderMetadata, AzureNetworkResponse } from 'azurecore';
|
import { AzureRestResponse, GetResourceGroupsResult, GetSubscriptionsResult, ResourceQueryResult, GetBlobContainersResult, GetFileSharesResult, HttpRequestMethod, GetLocationsResult, GetManagedDatabasesResult, CreateResourceGroupResult, GetBlobsResult, GetStorageAccountAccessKeyResult, AzureAccount, azureResource, AzureAccountProviderMetadata } from 'azurecore';
|
||||||
import { EOL } from 'os';
|
import { EOL } from 'os';
|
||||||
import { AppContext } from '../appContext';
|
import { AppContext } from '../appContext';
|
||||||
import { invalidAzureAccount, invalidTenant, unableToFetchTokenError } from '../localizedConstants';
|
import { invalidAzureAccount, invalidTenant, unableToFetchTokenError } from '../localizedConstants';
|
||||||
@@ -18,9 +18,6 @@ import { IAzureResourceSubscriptionFilterService, IAzureResourceSubscriptionServ
|
|||||||
import { AzureResourceGroupService } from './providers/resourceGroup/resourceGroupService';
|
import { AzureResourceGroupService } from './providers/resourceGroup/resourceGroupService';
|
||||||
import { BlobServiceClient, StorageSharedKeyCredential } from '@azure/storage-blob';
|
import { BlobServiceClient, StorageSharedKeyCredential } from '@azure/storage-blob';
|
||||||
import providerSettings from '../account-provider/providerSettings';
|
import providerSettings from '../account-provider/providerSettings';
|
||||||
import { getProxyEnabledHttpClient } from '../utils';
|
|
||||||
import { HttpClient } from '../account-provider/auths/httpClient';
|
|
||||||
import { NetworkRequestOptions } from '@azure/msal-common';
|
|
||||||
import { ErrorResponseBody } from '@azure/arm-subscriptions/esm/models';
|
import { ErrorResponseBody } from '@azure/arm-subscriptions/esm/models';
|
||||||
import { TenantIgnoredError } from '../utils/TenantIgnoredError';
|
import { TenantIgnoredError } from '../utils/TenantIgnoredError';
|
||||||
import { AzureMonitorResourceService } from './providers/azuremonitor/azuremonitorService';
|
import { AzureMonitorResourceService } from './providers/azuremonitor/azuremonitorService';
|
||||||
@@ -52,6 +49,7 @@ import { PostgresFlexibleServerTreeDataProvider } from './providers/postgresFlex
|
|||||||
import { PostgresFlexibleServerService } from './providers/postgresFlexibleServer/postgresFlexibleServerService';
|
import { PostgresFlexibleServerService } from './providers/postgresFlexibleServer/postgresFlexibleServerService';
|
||||||
import { CosmosDbPostgresTreeDataProvider } from './providers/cosmosdb/postgres/cosmosDbPostgresTreeDataProvider';
|
import { CosmosDbPostgresTreeDataProvider } from './providers/cosmosdb/postgres/cosmosDbPostgresTreeDataProvider';
|
||||||
import { CosmosDbPostgresService } from './providers/cosmosdb/postgres/cosmosDbPostgresService';
|
import { CosmosDbPostgresService } from './providers/cosmosdb/postgres/cosmosDbPostgresService';
|
||||||
|
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
@@ -463,7 +461,6 @@ export async function makeHttpRequest<B>(
|
|||||||
requestHeaders: Record<string, string> = {}
|
requestHeaders: Record<string, string> = {}
|
||||||
): Promise<AzureRestResponse<B>> {
|
): Promise<AzureRestResponse<B>> {
|
||||||
const result: AzureRestResponse<B> = { response: undefined, errors: [] };
|
const result: AzureRestResponse<B> = { response: undefined, errors: [] };
|
||||||
const httpClient: HttpClient = getProxyEnabledHttpClient();
|
|
||||||
|
|
||||||
if (!account?.properties?.tenants || !Array.isArray(account.properties.tenants)) {
|
if (!account?.properties?.tenants || !Array.isArray(account.properties.tenants)) {
|
||||||
const error = new Error(invalidAzureAccount);
|
const error = new Error(invalidAzureAccount);
|
||||||
@@ -509,10 +506,9 @@ export async function makeHttpRequest<B>(
|
|||||||
...requestHeaders
|
...requestHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
const body = JSON.stringify(requestBody || '');
|
const config: AxiosRequestConfig = {
|
||||||
let networkRequestOptions: NetworkRequestOptions = {
|
|
||||||
headers: reqHeaders,
|
headers: reqHeaders,
|
||||||
body
|
validateStatus: () => true // Never throw
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adding '/' if path does not begin with it.
|
// Adding '/' if path does not begin with it.
|
||||||
@@ -527,26 +523,22 @@ export async function makeHttpRequest<B>(
|
|||||||
requestUrl = `${account.properties.providerSettings.settings.armResource.endpoint}${path}`;
|
requestUrl = `${account.properties.providerSettings.settings.armResource.endpoint}${path}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let response: AzureNetworkResponse<B | ErrorResponseBodyWithError> | undefined = undefined;
|
let response: AxiosResponse<B | ErrorResponseBodyWithError> | undefined;
|
||||||
switch (requestType) {
|
switch (requestType) {
|
||||||
case HttpRequestMethod.GET:
|
case HttpRequestMethod.GET:
|
||||||
response = await httpClient.sendGetRequestAsync<B | ErrorResponseBodyWithError>(requestUrl, {
|
response = await axios.get(requestUrl, config);
|
||||||
headers: reqHeaders
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case HttpRequestMethod.POST:
|
case HttpRequestMethod.POST:
|
||||||
response = await httpClient.sendPostRequestAsync<B | ErrorResponseBodyWithError>(requestUrl, networkRequestOptions);
|
response = await axios.post(requestUrl, requestBody, config);
|
||||||
break;
|
break;
|
||||||
case HttpRequestMethod.PUT:
|
case HttpRequestMethod.PUT:
|
||||||
response = await httpClient.sendPutRequestAsync<B | ErrorResponseBodyWithError>(requestUrl, networkRequestOptions);
|
response = await axios.put(requestUrl, requestBody, config);
|
||||||
break;
|
break;
|
||||||
case HttpRequestMethod.DELETE:
|
case HttpRequestMethod.DELETE:
|
||||||
response = await httpClient.sendDeleteRequestAsync<B | ErrorResponseBodyWithError>(requestUrl, {
|
response = await axios.delete(requestUrl, config);
|
||||||
headers: reqHeaders
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case HttpRequestMethod.PATCH:
|
case HttpRequestMethod.PATCH:
|
||||||
response = await httpClient.sendPatchRequestAsync<B | ErrorResponseBodyWithError>(requestUrl, networkRequestOptions);
|
response = await axios.patch(requestUrl, config);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
const error = new Error(`Unknown RequestType "${requestType}"`);
|
const error = new Error(`Unknown RequestType "${requestType}"`);
|
||||||
@@ -576,7 +568,7 @@ export async function makeHttpRequest<B>(
|
|||||||
result.errors.push(error);
|
result.errors.push(error);
|
||||||
} else {
|
} else {
|
||||||
// We know this isn't an error response at this point
|
// We know this isn't an error response at this point
|
||||||
result.response = response as AzureNetworkResponse<B>;
|
result.response = response as AxiosResponse<B>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
14
extensions/azurecore/src/azurecore.d.ts
vendored
14
extensions/azurecore/src/azurecore.d.ts
vendored
@@ -8,6 +8,7 @@ declare module 'azurecore' {
|
|||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as msRest from '@azure/ms-rest-js';
|
import * as msRest from '@azure/ms-rest-js';
|
||||||
import { BlobItem } from '@azure/storage-blob';
|
import { BlobItem } from '@azure/storage-blob';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Covers defining what the azurecore extension exports to other extensions
|
* Covers defining what the azurecore extension exports to other extensions
|
||||||
@@ -273,17 +274,6 @@ declare module 'azurecore' {
|
|||||||
PATCH
|
PATCH
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom version of NetworkResponse from @azure\msal-common\dist\network\NetworkManager.d.ts
|
|
||||||
* with body renamed to data to avoid breaking changes with extensions. See
|
|
||||||
* https://github.com/microsoft/azuredatastudio/pull/22761 for details.
|
|
||||||
*/
|
|
||||||
export type AzureNetworkResponse<T> = {
|
|
||||||
headers: Record<string, string>;
|
|
||||||
data: T;
|
|
||||||
status: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface IExtension {
|
export interface IExtension {
|
||||||
/**
|
/**
|
||||||
* Gets the list of subscriptions for the specified AzureAccount
|
* Gets the list of subscriptions for the specified AzureAccount
|
||||||
@@ -350,7 +340,7 @@ declare module 'azurecore' {
|
|||||||
export type GetFileSharesResult = { fileShares: azureResource.FileShare[], errors: Error[] };
|
export type GetFileSharesResult = { fileShares: azureResource.FileShare[], errors: Error[] };
|
||||||
export type CreateResourceGroupResult = { resourceGroup: azureResource.AzureResourceResourceGroup | undefined, errors: Error[] };
|
export type CreateResourceGroupResult = { resourceGroup: azureResource.AzureResourceResourceGroup | undefined, errors: Error[] };
|
||||||
export type ResourceQueryResult<T extends azureResource.AzureGraphResource> = { resources: T[], errors: Error[] };
|
export type ResourceQueryResult<T extends azureResource.AzureGraphResource> = { resources: T[], errors: Error[] };
|
||||||
export type AzureRestResponse<B> = { response: AzureNetworkResponse<B> | undefined, errors: Error[] };
|
export type AzureRestResponse<B> = { response: AxiosResponse<B> | undefined, errors: Error[] };
|
||||||
export type GetBlobsResult = { blobs: azureResource.Blob[], errors: Error[] };
|
export type GetBlobsResult = { blobs: azureResource.Blob[], errors: Error[] };
|
||||||
export type GetStorageAccountAccessKeyResult = { keyName1: string, keyName2: string, errors: Error[] };
|
export type GetStorageAccountAccessKeyResult = { keyName1: string, keyName2: string, errors: Error[] };
|
||||||
export type CacheEncryptionKeys = { key: string; iv: string; }
|
export type CacheEncryptionKeys = { key: string; iv: string; }
|
||||||
|
|||||||
@@ -10,19 +10,12 @@ import * as constants from './constants';
|
|||||||
|
|
||||||
import { AzureRegion, azureResource } from 'azurecore';
|
import { AzureRegion, azureResource } from 'azurecore';
|
||||||
import { AppContext } from './appContext';
|
import { AppContext } from './appContext';
|
||||||
import { HttpClient } from './account-provider/auths/httpClient';
|
|
||||||
import { parse } from 'url';
|
|
||||||
import { getProxyAgentOptions } from './proxy';
|
|
||||||
import { HttpsProxyAgentOptions } from 'https-proxy-agent';
|
|
||||||
import { ProviderSettings, ProviderSettingsJson, SettingIds } from './account-provider/interfaces';
|
import { ProviderSettings, ProviderSettingsJson, SettingIds } from './account-provider/interfaces';
|
||||||
import { AzureResource } from 'azdata';
|
import { AzureResource } from 'azdata';
|
||||||
import { Logger } from './utils/Logger';
|
import { Logger } from './utils/Logger';
|
||||||
import { TelemetryAction, TelemetryReporter, TelemetryViews } from './telemetry';
|
import { TelemetryAction, TelemetryReporter, TelemetryViews } from './telemetry';
|
||||||
|
|
||||||
const localize = nls.loadMessageBundle();
|
const localize = nls.loadMessageBundle();
|
||||||
const configProxy = 'proxy';
|
|
||||||
const configProxyStrictSSL = 'proxyStrictSSL';
|
|
||||||
const configProxyAuthorization = 'proxyAuthorization';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a region value (@see AzureRegion) into the localized Display Name
|
* Converts a region value (@see AzureRegion) into the localized Display Name
|
||||||
@@ -145,10 +138,6 @@ export function getResourceTypeDisplayName(type: string): string {
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHttpConfiguration(): vscode.WorkspaceConfiguration {
|
|
||||||
return vscode.workspace.getConfiguration(constants.httpConfigSectionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets tenants to be ignored.
|
* Gets tenants to be ignored.
|
||||||
* @returns Tenants configured in ignore list
|
* @returns Tenants configured in ignore list
|
||||||
@@ -303,25 +292,6 @@ export interface IPackageInfo {
|
|||||||
aiKey: string;
|
aiKey: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProxyEnabledHttpClient(): HttpClient {
|
|
||||||
const proxy = <string>getHttpConfiguration().get(configProxy);
|
|
||||||
const strictSSL = getHttpConfiguration().get(configProxyStrictSSL, true);
|
|
||||||
const authorization = getHttpConfiguration().get(configProxyAuthorization);
|
|
||||||
|
|
||||||
const url = parse(proxy);
|
|
||||||
let agentOptions = getProxyAgentOptions(url, proxy, strictSSL);
|
|
||||||
|
|
||||||
if (authorization && url.protocol === 'https:') {
|
|
||||||
let httpsAgentOptions = agentOptions as HttpsProxyAgentOptions;
|
|
||||||
httpsAgentOptions!.headers = Object.assign(httpsAgentOptions!.headers || {}, {
|
|
||||||
'Proxy-Authorization': authorization
|
|
||||||
});
|
|
||||||
agentOptions = httpsAgentOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new HttpClient(proxy, agentOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display notification with button to reload
|
* Display notification with button to reload
|
||||||
* @param sectionName Name of section to reload
|
* @param sectionName Name of section to reload
|
||||||
|
|||||||
Reference in New Issue
Block a user