Skip to content

Commit

Permalink
Remove use of request package and update other dependencies (#2720)
Browse files Browse the repository at this point in the history
* Update version, packages

* Request be gone
  • Loading branch information
bwateratmsft authored Feb 12, 2021
1 parent a9bb25f commit ca57926
Show file tree
Hide file tree
Showing 24 changed files with 821 additions and 709 deletions.
1 change: 0 additions & 1 deletion extension.bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export { delay } from './src/utils/promiseUtils';
export { Lazy, AsyncLazy } from './src/utils/lazy';
export { bufferToString } from './src/utils/spawnAsync';
export { ext } from './src/extensionVariables';
export { httpsRequestBinary } from './src/utils/httpRequest';
export { inferCommand, inferPackageName, InspectMode, NodePackage } from './src/utils/nodeUtils';
export { nonNullProp } from './src/utils/nonNull';
export { getDockerOSType } from "./src/utils/osUtils";
Expand Down
862 changes: 488 additions & 374 deletions package-lock.json

Large diffs are not rendered by default.

38 changes: 19 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2706,21 +2706,22 @@
"@types/fs-extra": "^9.0.6",
"@types/glob": "^7.1.3",
"@types/mocha": "^8.2.0",
"@types/node": "^12.19.14",
"@types/node": "^12.19.16",
"@types/node-fetch": "^2.5.8",
"@types/request-promise-native": "^1.0.17",
"@types/semver": "^7.3.4",
"@types/string-replace-webpack-plugin": "^0.1.0",
"@types/tar-stream": "^2.2.0",
"@types/vscode": "1.53.0",
"@types/xml2js": "^0.4.7",
"@typescript-eslint/eslint-plugin": "^4.14.0",
"@typescript-eslint/eslint-plugin-tslint": "^4.14.0",
"@typescript-eslint/parser": "^4.14.0",
"adm-zip": "^0.5.1",
"@types/xml2js": "^0.4.8",
"@typescript-eslint/eslint-plugin": "^4.15.0",
"@typescript-eslint/eslint-plugin-tslint": "^4.15.0",
"@typescript-eslint/parser": "^4.15.0",
"adm-zip": "^0.5.2",
"copy-webpack-plugin": "^6.4.1",
"eslint": "^7.18.0",
"eslint": "^7.19.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-unicorn": "^26.0.1",
"eslint-plugin-unicorn": "^28.0.0",
"gulp": "^4.0.2",
"gulp-eslint": "^6.0.0",
"gulp-sourcemaps": "^3.0.0",
Expand All @@ -2730,36 +2731,35 @@
"ts-node": "^9.1.1",
"tslint": "^6.1.3",
"tslint-microsoft-contrib": "^6.2.0",
"typescript": "^4.1.3",
"typescript": "^4.1.4",
"umd-compat-loader": "^2.1.2",
"vsce": "^1.84.0",
"vscode-azureextensiondev": "^0.6.0",
"vsce": "^1.85.0",
"vscode-azureextensiondev": "^0.7.0",
"vscode-codicons": "^0.0.14",
"vscode-nls-dev": "^3.3.2",
"vscode-test": "^1.4.1",
"vscode-test": "^1.5.0",
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12"
},
"dependencies": {
"@azure/arm-appservice": "^6.1.0",
"@azure/arm-containerregistry": "^8.0.0",
"@azure/storage-blob": "^12.4.0",
"@azure/storage-blob": "^12.4.1",
"@docker/sdk": "^1.0.3",
"@grpc/grpc-js": "^1.2.3",
"dayjs": "^1.10.3",
"@grpc/grpc-js": "^1.2.6",
"dayjs": "^1.10.4",
"dockerfile-language-server-nodejs": "^0.3.0",
"dockerode": "^3.2.1",
"fs-extra": "^9.1.0",
"glob": "^7.1.6",
"gradle-to-js": "^2.0.0",
"handlebars": "^4.7.6",
"request": "^2.88.2",
"request-promise-native": "^1.0.9",
"node-fetch": "^2.6.1",
"semver": "^7.3.4",
"tar": "^6.1.0",
"tar-stream": "^2.2.0",
"vscode-azureappservice": "^0.72.2",
"vscode-azureextensionui": "^0.38.6",
"vscode-azureappservice": "^0.73.0",
"vscode-azureextensionui": "^0.39.2",
"vscode-languageclient": "^7.0.0",
"vscode-nls": "^5.0.0",
"vscode-tas-client": "^0.1.17",
Expand Down
5 changes: 2 additions & 3 deletions src/debugging/netcore/VsDbgHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import * as fse from 'fs-extra';
import * as os from 'os';
import * as path from 'path';
import * as request from 'request-promise-native';
import { ext } from '../../extensionVariables';
import { localize } from '../../localize';
import { streamToFile } from '../../utils/httpRequest';
import { isWindows } from '../../utils/osUtils';
import { execAsync } from '../../utils/spawnAsync';

Expand Down Expand Up @@ -60,8 +60,7 @@ async function getLatestAcquisitionScriptIfNecessary(): Promise<boolean> {
}

ext.outputChannel.appendLine(localize('vscode-docker.debugging.netCore.vsDbgHelper.acquiringScript', 'Acquiring latest VsDbg install script...'));
const responseStream = await request(acquisition.url);
await fse.writeFile(acquisition.scriptPath, responseStream);
await streamToFile(acquisition.url, acquisition.scriptPath);

await ext.context.globalState.update(scriptAcquiredDateKey, Date.now());
ext.outputChannel.appendLine(localize('vscode-docker.debugging.netCore.vsDbgHelper.scriptAcquired', 'Script acquired.'));
Expand Down
57 changes: 24 additions & 33 deletions src/dockerHubSearch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@

'use strict';

import https = require('https');
import { ociClientId } from './constants';
import { localize } from './localize';
import { httpsRequest } from './utils/httpRequest';
import { URL } from "url";
import { ociClientId } from "./constants";
import { localize } from "./localize";
import { httpRequest } from "./utils/httpRequest";

export function tagsForImage(image: IHubSearchResponseResult): string {
let tags: string[] = [];
Expand Down Expand Up @@ -82,15 +82,11 @@ export function searchImagesInRegistryHub(prefix: string, cache: boolean): Promi
// }
/* eslint-disable-next-line @typescript-eslint/promise-function-async */ // Grandfathered in
function invokeHubSearch(imageName: string, count: number, cache: boolean): Promise<IHubSearchResponse> {
const url = new URL('https://registry.hub.docker.com/v1/search');
url.searchParams.append('q', imageName);
url.searchParams.append('n', count.toString());
// https://registry.hub.docker.com/v1/search?q=redis&n=1
return fetchHttpsJson<IHubSearchResponse>(
{
hostname: 'registry.hub.docker.com',
port: 443,
path: '/v1/search?q=' + encodeURIComponent(imageName) + '&n=' + count,
method: 'GET',
},
cache);
return fetchHttpsJson<IHubSearchResponse>(url.toString(), cache);
}
export interface IHubSearchResponse {
/* eslint-disable-next-line camelcase */
Expand All @@ -117,31 +113,26 @@ export interface IHubSearchResponseResult {
}

/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
let JSON_CACHE: { [key: string]: Promise<any> } = {};
let JSON_CACHE: { [key: string]: any } = {};

/* eslint-disable-next-line @typescript-eslint/promise-function-async */ // Grandfathered in
function fetchHttpsJson<T>(opts: https.RequestOptions, cache: boolean): Promise<T> {
if (!cache) {
return doFetchHttpsJson(opts);
}
async function fetchHttpsJson<T>(url: string, cache: boolean): Promise<T> {
let cacheKey = url.toString();

let cacheKey = (opts.method + ' ' + opts.hostname + ' ' + opts.path);
if (!JSON_CACHE[cacheKey]) {
JSON_CACHE[cacheKey] = doFetchHttpsJson(opts);
if (!cache || !JSON_CACHE[cacheKey]) {
JSON_CACHE[cacheKey] = await doFetchHttpsJson(url);
}

// new promise to avoid cancelling
return new Promise<T>((resolve, reject) => {
JSON_CACHE[cacheKey].then(resolve, reject);
});
return JSON_CACHE[cacheKey] as T;
}

/* eslint-disable-next-line , @typescript-eslint/promise-function-async */ // Grandfathered in
function doFetchHttpsJson<T>(opts: https.RequestOptions): Promise<T> {
opts.headers = opts.headers || {};
opts.headers.Accept = 'application/json';
opts.headers['X-Meta-Source-Client'] = ociClientId;
return httpsRequest(opts).then((data) => {
return JSON.parse(data);
})
async function doFetchHttpsJson<T>(url: string): Promise<T> {
const options = {
headers: {
Accept: 'application/json',
'X-Meta-Source-Client': ociClientId,
}
}

const response = await httpRequest<T>(url.toString(), options);
return response.json();
}
42 changes: 15 additions & 27 deletions src/tree/images/imageChecker/OutdatedImageChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Response } from 'request';
import * as request from 'request-promise-native';
import * as vscode from 'vscode';
import { callWithTelemetryAndErrorHandling, IActionContext } from 'vscode-azureextensionui';
import { ociClientId } from '../../../constants';
import { DockerImage } from '../../../docker/Images';
import { ext } from '../../../extensionVariables';
import { httpRequest, RequestOptionsLike } from '../../../utils/httpRequest';
import { getImagePropertyValue } from '../ImageProperties';
import { DatedDockerImage } from '../ImagesTreeItem';
import { ImageRegistry, registries } from './registries';
Expand All @@ -19,19 +18,14 @@ const noneRegex = /<none>/i;
export class OutdatedImageChecker {
private shouldLoad: boolean;
private readonly outdatedImageIds: string[] = [];
private readonly defaultRequestOptions: request.RequestPromiseOptions;
private readonly defaultRequestOptions: RequestOptionsLike;

public constructor() {
const dockerConfig = vscode.workspace.getConfiguration('docker');
this.shouldLoad = dockerConfig.get('images.checkForOutdatedImages');

const httpSettings = vscode.workspace.getConfiguration('http');
const strictSSL = httpSettings.get<boolean>('proxyStrictSSL', true);
this.defaultRequestOptions = {
method: 'HEAD',
json: true,
resolveWithFullResponse: true,
strictSSL: strictSSL,
headers: {
'X-Meta-Source-Client': ociClientId,
'Accept': 'application/vnd.docker.distribution.manifest.list.v2+json',
Expand Down Expand Up @@ -94,17 +88,11 @@ export class OutdatedImageChecker {
return 'unknown';
}

let token: string | undefined;
// 0. If there's a method to sign the request, it will be called on the registry
// 1. Get the latest image digest ID from the manifest
const latestImageDigest = await this.getLatestImageDigest(registry, repo, tag);

// 1. Get an OAuth token to access the resource. No Authorization header is required for public scopes.
if (registry.getToken) {
token = await registry.getToken({ ...this.defaultRequestOptions, method: 'GET' }, `repository:library/${repo}:pull`);
}

// 2. Get the latest image digest ID from the manifest
const latestImageDigest = await this.getLatestImageDigest(registry, repo, tag, token);

// 3. Compare it with the current image's value
// 2. Compare it with the current image's value
const imageInspectInfo = await ext.dockerClient.inspectImage(context, image.Id);

if (imageInspectInfo?.RepoDigests?.[0]?.toLowerCase()?.indexOf(latestImageDigest.toLowerCase()) < 0) {
Expand All @@ -117,15 +105,15 @@ export class OutdatedImageChecker {
}
}

private async getLatestImageDigest(registry: ImageRegistry, repo: string, tag: string, oAuthToken: string | undefined): Promise<string> {
const manifestOptions: request.RequestPromiseOptions = {
...this.defaultRequestOptions,
auth: oAuthToken ? {
bearer: oAuthToken,
} : undefined,
};
private async getLatestImageDigest(registry: ImageRegistry, repo: string, tag: string): Promise<string> {
const manifestResponse = await httpRequest(`${registry.baseUrl}/${repo}/manifests/${tag}`, this.defaultRequestOptions, async (request) => {
if (registry.signRequest) {
return registry.signRequest(request, `repository:library/${repo}:pull`);
}

return request;
});

const manifestResponse = await request(`${registry.baseUrl}/${repo}/manifests/${tag}`, manifestOptions) as Response;
return manifestResponse.headers['docker-content-digest'] as string;
return manifestResponse.headers['docker-content-digest'];
}
}
51 changes: 27 additions & 24 deletions src/tree/images/imageChecker/registries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Response } from 'request';
import * as request from 'request-promise-native';
import { localize } from '../../../localize';
import { IOAuthContext } from '../../registries/auth/IAuthProvider';
import { getWwwAuthenticateContext } from '../../registries/auth/oAuthUtils';
import { URL } from 'url';
import { ociClientId } from '../../../constants';
import { bearerAuthHeader, getWwwAuthenticateContext, HttpErrorResponse, httpRequest, IOAuthContext, RequestLike, RequestOptionsLike } from '../../../utils/httpRequest';

export interface ImageRegistry {
registryMatch: RegExp;
baseUrl: string;
getToken?(requestOptions: request.RequestPromiseOptions, scope: string): Promise<string>;
signRequest?(request: RequestLike, scope: string): Promise<RequestLike>;
}

let dockerHubAuthContext: IOAuthContext | undefined;
Expand All @@ -21,38 +19,43 @@ export const registries: ImageRegistry[] = [
{
registryMatch: /docker[.]io\/library/i,
baseUrl: 'https://registry-1.docker.io/v2/library',
getToken: async (requestOptions: request.RequestPromiseOptions, scope: string): Promise<string> => {
signRequest: async (request: RequestLike, scope: string): Promise<RequestLike> => {
if (!dockerHubAuthContext) {
try {
await request('https://registry-1.docker.io/v2/', requestOptions);
} catch (err) {
const result = getWwwAuthenticateContext(err);
const options: RequestOptionsLike = {
headers: {
'X-Meta-Source-Client': ociClientId,
},
};

if (!result) {
throw err;
await httpRequest('https://registry-1.docker.io/v2/', options);
} catch (error) {
if (!(error instanceof HttpErrorResponse) ||
!(dockerHubAuthContext = getWwwAuthenticateContext(error))) {
// If it's not an HttpErrorResponse, or getWwwAuthenticateContext came back undefined, rethrow
throw error;
}

dockerHubAuthContext = result;
}
}

const authOptions: request.RequestPromiseOptions = {
...requestOptions,
qs: {
const authRequestOptions: RequestOptionsLike = {
method: 'GET',
headers: {
'X-Meta-Source-Client': ociClientId,
service: dockerHubAuthContext.service,
scope: scope,
},
};

const tokenResponse = await request(dockerHubAuthContext.realm.toString(), authOptions) as Response;
// eslint-disable-next-line @typescript-eslint/tslint/config
const token: string = tokenResponse?.body?.token;
const url = new URL(dockerHubAuthContext.realm.toString());
url.searchParams.append('service', dockerHubAuthContext.service);
url.searchParams.append('scope', scope);

if (!token) {
throw new Error(localize('vscode-docker.outdatedImageChecker.noToken', 'Failed to acquire Docker Hub OAuth token for scope: \'{0}\'', scope));
}
const tokenResponse = await httpRequest<{ token: string }>(url.toString(), authRequestOptions);
const token = (await tokenResponse.json()).token;

return token;
request.headers.set('Authorization', bearerAuthHeader(token));
return request;
}
},
{
Expand Down
4 changes: 2 additions & 2 deletions src/tree/registries/RegistryTreeItemBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { RequestPromiseOptions } from "request-promise-native";
import { ThemeIcon } from "vscode";
import { AzExtParentTreeItem } from "vscode-azureextensionui";
import { RequestLike } from "../../utils/httpRequest";
import { IRegistryAuthTreeItem } from "../../utils/registryRequestUtils";
import { getRegistryContextValue, registrySuffix } from "./registryContextValues";

Expand Down Expand Up @@ -41,7 +41,7 @@ export abstract class RegistryTreeItemBase extends AzExtParentTreeItem implement
/**
* This will be called before each registry request to add authentication
*/
public abstract addAuth(options: RequestPromiseOptions): Promise<void>;
public abstract signRequest(request: RequestLike): Promise<RequestLike>;

/**
* Describes credentials used to log in to the docker cli before pushing or pulling an image
Expand Down
4 changes: 2 additions & 2 deletions src/tree/registries/RemoteRepositoryTreeItemBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { RequestPromiseOptions } from "request-promise-native";
import { ThemeIcon } from "vscode";
import { AzExtParentTreeItem, AzExtTreeItem } from "vscode-azureextensionui";
import { RequestLike } from "../../utils/httpRequest";
import { IRepositoryAuthTreeItem } from "../../utils/registryRequestUtils";
import { getRegistryContextValue, repositorySuffix } from "./registryContextValues";
import { RegistryTreeItemBase } from "./RegistryTreeItemBase";
Expand Down Expand Up @@ -37,7 +37,7 @@ export abstract class RemoteRepositoryTreeItemBase extends AzExtParentTreeItem i
* Optional method to implement if repo-level requests should have different authentication than registry-level requests
* For example, if the registry supports OAuth you might get a token that has just repo-level permissions instead of registry-level permissions
*/
public addAuth?(options: RequestPromiseOptions): Promise<void>;
public signRequest?(request: RequestLike): Promise<RequestLike>;

public compareChildrenImpl(ti1: AzExtTreeItem, ti2: AzExtTreeItem): number {
if (ti1 instanceof RemoteTagTreeItem && ti2 instanceof RemoteTagTreeItem) {
Expand Down
Loading

0 comments on commit ca57926

Please sign in to comment.