From a9b1091191acd5268d039a7374ee6be8c639fc37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Justin=20Holgu=C3=ADn?= Date: Fri, 13 Dec 2024 22:51:42 +0000 Subject: [PATCH] WIP: add proxy support --- package-lock.json | 176 +++++++++++++++++++++++++++++++++++-- package.json | 2 + src/snapshot.proxy.test.ts | 106 ++++++++++++++++++++++ src/snapshot.ts | 10 ++- 4 files changed, 285 insertions(+), 9 deletions(-) create mode 100644 src/snapshot.proxy.test.ts diff --git a/package-lock.json b/package-lock.json index d075260..47c7114 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,8 +22,10 @@ "@biomejs/biome": "^1.7.0", "@tsconfig/strictest": "^2.0.5", "@types/node": "^20.12.7", + "proxy": "^2.0.0", "tsup": "^8.0.2", "typescript": "^5.4.5", + "undici": "^6.0.0", "vitest": "^1.5.0" } }, @@ -78,6 +80,18 @@ "undici": "^5.25.4" } }, + "node_modules/@actions/http-client/node_modules/undici": { + "version": "5.28.4", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", + "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/@actions/io": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", @@ -610,6 +624,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", "engines": { "node": ">=14" } @@ -1294,6 +1309,22 @@ "node": ">= 8" } }, + "node_modules/args": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/args/-/args-5.0.3.tgz", + "integrity": "sha512-h6k/zfFgusnv3i5TU08KQkVKuCPBtL/PWQbWkHUxvJrZ2nAyeaUupneemcrgn1xmqxPQsPIzwkUhOpoqPDRZuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "5.0.0", + "chalk": "2.4.2", + "leven": "2.1.0", + "mri": "1.1.4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -1318,6 +1349,12 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/basic-auth-parser": { + "version": "0.0.2-1", + "resolved": "https://registry.npmjs.org/basic-auth-parser/-/basic-auth-parser-0.0.2-1.tgz", + "integrity": "sha512-GFj8iVxo9onSU6BnnQvVwqvxh60UcSHJEDnIk3z4B6iOjsKSmqe+ibW0Rsz7YO7IE1HG3D3tqCNIidP46SZVdQ==", + "dev": true + }, "node_modules/before-after-hook": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", @@ -1380,6 +1417,16 @@ "node": ">=8" } }, + "node_modules/camelcase": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", + "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chai": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", @@ -1398,6 +1445,51 @@ "node": ">=4" } }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/chalk/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -1586,6 +1678,16 @@ "@esbuild/win32-x64": "0.19.12" } }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -1772,6 +1874,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -1892,6 +2004,16 @@ "integrity": "sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==", "dev": true }, + "node_modules/leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/lilconfig": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", @@ -2041,6 +2163,16 @@ "ufo": "^1.3.2" } }, + "node_modules/mri": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz", + "integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2313,6 +2445,24 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/proxy": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/proxy/-/proxy-2.2.0.tgz", + "integrity": "sha512-nYclNIWj9UpXbVJ3W5EXIYiGR88AKZoGt90kyh3zoOBY5QW+7bbtPvMFgKGD4VJmpS3UXQXtlGXSg3lRNLOFLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "args": "^5.0.3", + "basic-auth-parser": "0.0.2-1", + "debug": "^4.3.4" + }, + "bin": { + "proxy": "dist/bin/proxy.js" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2651,6 +2801,19 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -2818,14 +2981,13 @@ "dev": true }, "node_modules/undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz", + "integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=14.0" + "node": ">=18.17" } }, "node_modules/undici-types": { diff --git a/package.json b/package.json index fd33ae6..e5a02ae 100644 --- a/package.json +++ b/package.json @@ -44,10 +44,12 @@ }, "devDependencies": { "@biomejs/biome": "^1.7.0", + "proxy": "^2.0.0", "@tsconfig/strictest": "^2.0.5", "@types/node": "^20.12.7", "tsup": "^8.0.2", "typescript": "^5.4.5", + "undici": "^6.0.0", "vitest": "^1.5.0" } } diff --git a/src/snapshot.proxy.test.ts b/src/snapshot.proxy.test.ts new file mode 100644 index 0000000..31b287c --- /dev/null +++ b/src/snapshot.proxy.test.ts @@ -0,0 +1,106 @@ +/*! + * Tests are based on work by Nathan Rajlich: + * https://github.com/TooTallNate/node-http-proxy-agent/blob/65307ac8fe4e6ce1a2685d21ec4affa4c2a0a30d/test/test.js + * Copyright (c) 2013 Nathan Rajlich + * Released under the MIT license + * + * and on work by Rafael Gonzaga (https://github.com/RafaelGSS) + * + * https://github.com/nodejs/undici/blob/512cdadc403874571cd5035a6c41debab1165310/test/proxy-agent.js#L370-L418 + * Released under the MIT license + */ +import { Server, createServer } from "node:http"; +import { type AddressInfo } from "node:net"; +import { type ProxyServer, createProxy } from "proxy"; +import { ProxyAgent, fetch as undiciFetch } from "undici"; +import { Octokit } from "@octokit/core"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { Snapshot, submitSnapshot } from './snapshot.js' +import { context } from '@actions/github' +import * as core from '@actions/core' + +describe("client proxy", () => { + let server: Server; + let proxyServer: ProxyServer; + let serverUrl: string; + let proxyUrl: string; + + beforeEach(() => { + server = createServer(); + server.listen(0, () => {}); + + proxyServer = createProxy(); + proxyServer.listen(0, () => {}); + + serverUrl = `http://localhost:${(server.address() as AddressInfo).port}`; + proxyUrl = `http://localhost:${ + (proxyServer.address() as AddressInfo).port + }`; + }); + + it("options.request.fetch = customFetch with dispatcher: new ProxyAgent(proxyUrl)", async () => { + let proxyConnectionEstablished = false; + + // requests are not exposed to the proxy server, they are tunneled to + // Reference: https://github.com/advisories/GHSA-pgw7-wx7w-2w33 + // Commit: https://github.com/nodejs/undici/commit/df4f7e0e95f5112322a96fd7a666cb28c1d48327#diff-90964a82994d6c63f28161d5410c64406e6abdee4ac0759e83b1abbbe469cda4L35-R39 + proxyServer.on("connect", () => { + core.notice(`proxyServer.on("connect")`); + proxyConnectionEstablished = true; + }); + + server.on("request", (request, response) => { + core.notice(`request: ${request}`); + expect(request.method).toEqual("GET"); + expect(request.url).toEqual("/"); + expect(request.headers.accept).toBe("application/vnd.github.v3+json"); + + response.writeHead(200); + // return a body containing the expected JSON: {"value": "foo"} + response.write(JSON.stringify({ value: "foo" })); + response.end(); + }); + + // const myFetch: typeof undiciFetch = (url, opts) => { + // return undiciFetch(url, { + // ...opts, + // dispatcher: new ProxyAgent({ + // uri: proxyUrl, + // keepAliveTimeout: 10, + // keepAliveMaxTimeout: 10, + // }), + // }); + // }; + + // const octokit = new Octokit({ + // baseUrl: serverUrl, + // request: { fetch: myFetch }, + // }); + + // await octokit.request("/"); + const snapshot = new Snapshot( + {name: 'example-detector', url: 'http://example.com', version: '1.0.0'}, + context, + {id: 'job', correlator: 'correlator'}, + new Date() + ) + + await submitSnapshot(snapshot, context, + new ProxyAgent({ + uri: proxyUrl, + keepAliveTimeout: 10, + keepAliveMaxTimeout: 10, + }) + ); + + + expect(proxyConnectionEstablished).toBeTruthy(); + expect.assertions(4); + }); + + afterEach(() => { + server.close(); + proxyServer.close(); + }); +}); diff --git a/src/snapshot.ts b/src/snapshot.ts index 283067c..4b04199 100644 --- a/src/snapshot.ts +++ b/src/snapshot.ts @@ -3,6 +3,7 @@ import * as github from '@actions/github' import type { Context } from '@actions/github/lib/context.js' import { RequestError } from '@octokit/request-error' import type { PullRequestEvent } from '@octokit/webhooks-types' +import { ProxyAgent } from "undici"; import type { Manifest } from './manifest.js' @@ -164,14 +165,16 @@ export class Snapshot { */ export async function submitSnapshot( snapshot: Snapshot, - context: Context = github.context + context: Context = github.context, + proxyAgent?: ProxyAgent ) { core.setOutput('snapshot', JSON.stringify(snapshot)) core.notice('Submitting snapshot...') core.notice(snapshot.prettyJSON()) const repo = context.repo - const githubToken = core.getInput('token') || (await core.getIDToken()) + // TODO: remove "foo" from below, obviously. It would be better to somehow set the 'token' input value in tests. + const githubToken = "foo" || core.getInput('token') || (await core.getIDToken()) const octokit = github.getOctokit(githubToken) try { @@ -183,6 +186,9 @@ export async function submitSnapshot( }, owner: repo.owner, repo: repo.repo, + request: { + agent: proxyAgent + }, ...snapshot } )