Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove use of request package and update other dependencies #2720

Merged
merged 15 commits into from
Feb 12, 2021
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
864 changes: 489 additions & 375 deletions package-lock.json

Large diffs are not rendered by default.

40 changes: 20 additions & 20 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vscode-docker",
"version": "1.10.0",
"version": "1.10.5-alpha",
"publisher": "ms-azuretools",
"displayName": "Docker",
"description": "Makes it easy to create, manage, and debug containerized applications.",
Expand Down 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
Loading