From dbad973ee538526819c65e1fefdc2e3a5e81dec0 Mon Sep 17 00:00:00 2001 From: Jurre Stender Date: Wed, 12 Aug 2026 15:27:45 +0200 Subject: [PATCH 1/3] Wait for proxy readiness before updates Probe port 1080 from inside the proxy container before starting the updater, with a bounded timeout and deterministic coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c7891dcd-6a84-4edc-a6e7-47e7a4b9c37d --- __tests__/proxy-integration.test.ts | 1 + __tests__/proxy.test.ts | 80 +++++++++++++++++++++++++++++ __tests__/updater.test.ts | 40 +++++++++++++-- dist/cleanup.js | 8 +-- dist/main.js | 33 +++++++++--- src/proxy.ts | 26 ++++++++++ src/updater.ts | 1 + 7 files changed, 170 insertions(+), 19 deletions(-) create mode 100644 __tests__/proxy.test.ts diff --git a/__tests__/proxy-integration.test.ts b/__tests__/proxy-integration.test.ts index fab0ab372..a607c4b9a 100644 --- a/__tests__/proxy-integration.test.ts +++ b/__tests__/proxy-integration.test.ts @@ -42,6 +42,7 @@ integration('ProxyBuilder', () => { credentials ) await proxy.container.start() + await proxy.waitUntilReady() const containerInfo = await proxy.container.inspect() expect(containerInfo.Name).toBe('/dependabot-job-1-proxy') diff --git a/__tests__/proxy.test.ts b/__tests__/proxy.test.ts new file mode 100644 index 000000000..ad16caaa1 --- /dev/null +++ b/__tests__/proxy.test.ts @@ -0,0 +1,80 @@ +import Docker, {Container, Network} from 'dockerode' +import {PassThrough} from 'node:stream' +import {ContainerService} from '../src/container-service' +import {Proxy, ProxyBuilder} from '../src/proxy' + +type ProxyTestResources = { + container: Container + proxy: Proxy +} + +async function buildProxy(): Promise { + const container = { + id: 'proxy-container', + attach: jest.fn().mockResolvedValue(new PassThrough()), + modem: {demuxStream: jest.fn()}, + putArchive: jest.fn().mockResolvedValue(undefined), + inspect: jest + .fn() + .mockRejectedValue(new Error('host cannot reach the internal IP')), + start: jest.fn().mockResolvedValue(undefined), + stop: jest.fn().mockResolvedValue(undefined), + remove: jest.fn().mockResolvedValue(undefined) + } as unknown as Container + const externalNetwork = { + connect: jest.fn().mockResolvedValue(undefined), + remove: jest.fn().mockResolvedValue(undefined) + } as unknown as Network + const internalNetwork = { + remove: jest.fn().mockResolvedValue(undefined) + } as unknown as Network + const docker = { + listNetworks: jest.fn().mockResolvedValue([]), + createNetwork: jest + .fn() + .mockResolvedValueOnce(externalNetwork) + .mockResolvedValueOnce(internalNetwork), + createContainer: jest.fn().mockResolvedValue(container) + } as unknown as Docker + const proxy = await new ProxyBuilder(docker, 'proxy-image', false).run( + 1, + 'job-token', + 'https://dependabot-api.example.com', + [] + ) + + return { + container, + proxy + } +} + +describe('Proxy readiness', () => { + it('checks readiness inside the proxy container network namespace', async () => { + const {container, proxy} = await buildProxy() + const execCommand = jest + .spyOn(ContainerService, 'execCommand') + .mockResolvedValue(undefined) + + await expect(proxy.waitUntilReady()).resolves.toBeUndefined() + expect(execCommand).toHaveBeenCalledWith( + container, + expect.arrayContaining([ + expect.stringContaining('127.0.0.1'), + expect.stringContaining('1080') + ]), + 'root' + ) + }) + + it('reports an actionable error when the in-container probe times out', async () => { + const {proxy} = await buildProxy() + jest + .spyOn(ContainerService, 'execCommand') + .mockRejectedValue(new Error('Command exited with code 124')) + + await expect(proxy.waitUntilReady()).rejects.toThrow( + 'Proxy did not start accepting connections on port 1080 within 60 seconds' + ) + }) +}) diff --git a/__tests__/updater.test.ts b/__tests__/updater.test.ts index 0a60afc6e..c401aa99e 100644 --- a/__tests__/updater.test.ts +++ b/__tests__/updater.test.ts @@ -35,6 +35,7 @@ describe('Updater', () => { container: { start: jest.fn() }, + waitUntilReady: jest.fn(), network: jest.fn(), networkName: 'mockNetworkName', url: () => { @@ -67,16 +68,45 @@ describe('Updater', () => { .mockResolvedValue(mockContainer) jest.spyOn(ProxyBuilder.prototype, 'run').mockResolvedValue(mockProxy) - jest.spyOn(ContainerService, 'run').mockImplementationOnce( - jest.fn(async () => { - return true - }) - ) + mockProxy.waitUntilReady.mockResolvedValue(undefined) + jest + .spyOn(ContainerService, 'run') + .mockImplementation(jest.fn(async () => true)) }) it('should be successful', async () => { expect(await updater.runUpdater()).toBe(true) }) + + it('does not start the updater until the proxy is ready', async () => { + const {promise, resolve} = Promise.withResolvers() + mockProxy.waitUntilReady.mockReturnValueOnce(promise) + + const runPromise = updater.runUpdater() + await new Promise(resolveImmediate => + setImmediate(resolveImmediate) + ) + + try { + expect(jest.mocked(ContainerService).run.mock.calls).toHaveLength(0) + } finally { + resolve() + } + + await expect(runPromise).resolves.toBe(true) + }) + + it('cleans up when the proxy does not become ready', async () => { + mockProxy.waitUntilReady.mockRejectedValueOnce( + new Error('proxy readiness timed out') + ) + + await expect(updater.runUpdater()).rejects.toThrow( + 'proxy readiness timed out' + ) + expect(jest.mocked(ContainerService).run.mock.calls).toHaveLength(0) + expect(mockProxy.shutdown.mock.calls).toHaveLength(1) + }) }) describe('when the updater container fails', () => { diff --git a/dist/cleanup.js b/dist/cleanup.js index 969ce63b8..468242832 100644 --- a/dist/cleanup.js +++ b/dist/cleanup.js @@ -24295,12 +24295,6 @@ var require_utils4 = __commonJS({ } }); -// node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node -var require_sshcrypto = __commonJS({ - "node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node"() { - } -}); - // node_modules/ssh2/lib/protocol/crypto/poly1305.js var require_poly1305 = __commonJS({ "node_modules/ssh2/lib/protocol/crypto/poly1305.js"(exports2, module2) { @@ -24755,7 +24749,7 @@ var require_crypto = __commonJS({ var ChaChaPolyDecipher; var GenericDecipher; try { - binding = require_sshcrypto(); + binding = require("./crypto/build/Release/sshcrypto.node"); ({ AESGCMCipher, ChaChaPolyCipher, diff --git a/dist/main.js b/dist/main.js index 18a8f6657..5dda5756f 100644 --- a/dist/main.js +++ b/dist/main.js @@ -29059,12 +29059,6 @@ var require_utils6 = __commonJS({ } }); -// node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node -var require_sshcrypto = __commonJS({ - "node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node"() { - } -}); - // node_modules/ssh2/lib/protocol/crypto/poly1305.js var require_poly1305 = __commonJS({ "node_modules/ssh2/lib/protocol/crypto/poly1305.js"(exports2, module2) { @@ -29519,7 +29513,7 @@ var require_crypto = __commonJS({ var ChaChaPolyDecipher; var GenericDecipher; try { - binding = require_sshcrypto(); + binding = require("./crypto/build/Release/sshcrypto.node"); ({ AESGCMCipher, ChaChaPolyCipher, @@ -99472,6 +99466,7 @@ var CONFIG_FILE_PATH = "/"; var CONFIG_FILE_NAME = "config.json"; var CA_CERT_INPUT_PATH = "/usr/local/share/ca-certificates"; var CUSTOM_CA_CERT_NAME = "custom-ca-cert.crt"; +var PROXY_READY_TIMEOUT_SECONDS = 60; var CERT_SUBJECT = [ { name: "commonName", @@ -99560,11 +99555,34 @@ var ProxyBuilder = class { throw new Error("proxy container isn't running"); } }; + const waitUntilReady = async () => { + try { + await ContainerService.execCommand( + container, + [ + "timeout", + `${PROXY_READY_TIMEOUT_SECONDS}`, + "sh", + "-c", + 'until nc -w 1 "$0" "$1" { await container.stop(); @@ -99805,6 +99823,7 @@ var Updater = class { ); await proxy.container.start(); try { + await proxy.waitUntilReady(); await this.runUpdate(proxy); return true; } finally { diff --git a/src/proxy.ts b/src/proxy.ts index 42992d044..5d1b6e956 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -13,6 +13,7 @@ const CONFIG_FILE_PATH = '/' const CONFIG_FILE_NAME = 'config.json' const CA_CERT_INPUT_PATH = '/usr/local/share/ca-certificates' const CUSTOM_CA_CERT_NAME = 'custom-ca-cert.crt' +const PROXY_READY_TIMEOUT_SECONDS = 60 const CERT_SUBJECT = [ { name: 'commonName', @@ -45,6 +46,7 @@ export type Proxy = { network: Network networkName: string url: () => Promise + waitUntilReady: () => Promise cert: string shutdown: () => Promise } @@ -126,11 +128,35 @@ export class ProxyBuilder { } } + const waitUntilReady = async (): Promise => { + try { + await ContainerService.execCommand( + container, + [ + 'timeout', + `${PROXY_READY_TIMEOUT_SECONDS}`, + 'sh', + '-c', + 'until nc -w 1 "$0" "$1" { await container.stop() diff --git a/src/updater.ts b/src/updater.ts index 2a387bc36..5598e3d09 100644 --- a/src/updater.ts +++ b/src/updater.ts @@ -43,6 +43,7 @@ export class Updater { await proxy.container.start() try { + await proxy.waitUntilReady() await this.runUpdate(proxy) return true } finally { From e4b294c66e59b042c12fd4cd53319d47df36b24f Mon Sep 17 00:00:00 2001 From: Jurre Stender Date: Wed, 12 Aug 2026 15:28:40 +0200 Subject: [PATCH 2/3] Preserve proxy failures during cleanup Treat an already-stopped proxy as idempotent cleanup, remove resources in dependency order, and report cleanup failures without masking the update error. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c7891dcd-6a84-4edc-a6e7-47e7a4b9c37d --- __tests__/proxy.test.ts | 155 ++++++++++++++- dist/main.js | 405 ++++++++++++++++++++++------------------ src/proxy.ts | 39 +++- src/updater.ts | 22 ++- 4 files changed, 424 insertions(+), 197 deletions(-) diff --git a/__tests__/proxy.test.ts b/__tests__/proxy.test.ts index ad16caaa1..53776da40 100644 --- a/__tests__/proxy.test.ts +++ b/__tests__/proxy.test.ts @@ -1,14 +1,32 @@ +import * as core from '@actions/core' import Docker, {Container, Network} from 'dockerode' import {PassThrough} from 'node:stream' +import {ApiClient, JobDetails} from '../src/api-client' import {ContainerService} from '../src/container-service' import {Proxy, ProxyBuilder} from '../src/proxy' +import {Updater} from '../src/updater' type ProxyTestResources = { container: Container + containerRemove: jest.Mock + externalNetworkRemove: jest.Mock + internalNetworkRemove: jest.Mock proxy: Proxy } -async function buildProxy(): Promise { +const alreadyStoppedError = (): Error => + Object.assign(new Error('container is already stopped'), {statusCode: 304}) + +async function buildProxyWithStopError( + stopError: Error, + containerRemoveError?: Error, + containerRemoveOverride?: jest.Mock +): Promise { + const containerRemove = + containerRemoveOverride ?? + (containerRemoveError + ? jest.fn().mockRejectedValue(containerRemoveError) + : jest.fn().mockResolvedValue(undefined)) const container = { id: 'proxy-container', attach: jest.fn().mockResolvedValue(new PassThrough()), @@ -18,15 +36,17 @@ async function buildProxy(): Promise { .fn() .mockRejectedValue(new Error('host cannot reach the internal IP')), start: jest.fn().mockResolvedValue(undefined), - stop: jest.fn().mockResolvedValue(undefined), - remove: jest.fn().mockResolvedValue(undefined) + stop: jest.fn().mockRejectedValue(stopError), + remove: containerRemove } as unknown as Container + const externalNetworkRemove = jest.fn().mockResolvedValue(undefined) const externalNetwork = { connect: jest.fn().mockResolvedValue(undefined), - remove: jest.fn().mockResolvedValue(undefined) + remove: externalNetworkRemove } as unknown as Network + const internalNetworkRemove = jest.fn().mockResolvedValue(undefined) const internalNetwork = { - remove: jest.fn().mockResolvedValue(undefined) + remove: internalNetworkRemove } as unknown as Network const docker = { listNetworks: jest.fn().mockResolvedValue([]), @@ -45,13 +65,53 @@ async function buildProxy(): Promise { return { container, + containerRemove, + externalNetworkRemove, + internalNetworkRemove, proxy } } +function buildUpdaterWithProxy(proxy: Proxy): { + updater: Updater + restoreProxyBuilder: () => void +} { + const proxyBuilderRun = jest + .spyOn(ProxyBuilder.prototype, 'run') + .mockResolvedValue(proxy) + const apiClient = { + params: { + jobId: 1, + dependabotApiUrl: 'https://dependabot-api.example.com' + }, + getJobToken: jest.fn().mockReturnValue('job-token') + } as unknown as ApiClient + const jobDetails: JobDetails = { + id: '1', + 'allowed-updates': [], + 'package-manager': 'npm_and_yarn', + 'credentials-metadata': [], + experiments: {}, + source: {repo: 'github/dependabot-action'} + } + + return { + updater: new Updater( + 'updater-image', + 'proxy-image', + apiClient, + jobDetails, + [] + ), + restoreProxyBuilder: () => proxyBuilderRun.mockRestore() + } +} + describe('Proxy readiness', () => { it('checks readiness inside the proxy container network namespace', async () => { - const {container, proxy} = await buildProxy() + const {container, proxy} = await buildProxyWithStopError( + alreadyStoppedError() + ) const execCommand = jest .spyOn(ContainerService, 'execCommand') .mockResolvedValue(undefined) @@ -68,7 +128,7 @@ describe('Proxy readiness', () => { }) it('reports an actionable error when the in-container probe times out', async () => { - const {proxy} = await buildProxy() + const {proxy} = await buildProxyWithStopError(alreadyStoppedError()) jest .spyOn(ContainerService, 'execCommand') .mockRejectedValue(new Error('Command exited with code 124')) @@ -78,3 +138,84 @@ describe('Proxy readiness', () => { ) }) }) + +describe('Proxy shutdown', () => { + it('preserves a readiness error and removes resources when the proxy is already stopped', async () => { + const { + containerRemove, + externalNetworkRemove, + internalNetworkRemove, + proxy + } = await buildProxyWithStopError(alreadyStoppedError()) + const readinessError = new Error('proxy readiness timed out') + proxy.waitUntilReady = jest.fn().mockRejectedValue(readinessError) + const {updater, restoreProxyBuilder} = buildUpdaterWithProxy(proxy) + + try { + await expect(updater.runUpdater()).rejects.toBe(readinessError) + expect(containerRemove.mock.calls).toHaveLength(1) + expect(externalNetworkRemove.mock.calls).toHaveLength(1) + expect(internalNetworkRemove.mock.calls).toHaveLength(1) + } finally { + restoreProxyBuilder() + } + }) + + it('preserves a readiness error when unexpected cleanup fails', async () => { + const stopError = Object.assign(new Error('Docker API unavailable'), { + statusCode: 500 + }) + const removeError = new Error('container removal failed') + const { + containerRemove, + externalNetworkRemove, + internalNetworkRemove, + proxy + } = await buildProxyWithStopError(stopError, removeError) + const readinessError = new Error('proxy readiness timed out') + proxy.waitUntilReady = jest.fn().mockRejectedValue(readinessError) + const {updater, restoreProxyBuilder} = buildUpdaterWithProxy(proxy) + const info = jest.spyOn(core, 'info').mockImplementation() + + try { + await expect(updater.runUpdater()).rejects.toBe(readinessError) + expect(containerRemove.mock.calls).toHaveLength(1) + expect(externalNetworkRemove.mock.calls).toHaveLength(1) + expect(internalNetworkRemove.mock.calls).toHaveLength(1) + expect(info).toHaveBeenCalledWith( + expect.stringContaining('Docker API unavailable') + ) + expect(info).toHaveBeenCalledWith( + expect.stringContaining('container removal failed') + ) + } finally { + info.mockRestore() + restoreProxyBuilder() + } + }) + + it('sequences cleanup and aggregates failures after attempting every step', async () => { + const stopError = Object.assign(new Error('Docker API unavailable'), { + statusCode: 500 + }) + const removeError = new Error('container removal failed') + const {promise, reject} = Promise.withResolvers() + const containerRemove = jest.fn().mockReturnValue(promise) + const {externalNetworkRemove, internalNetworkRemove, proxy} = + await buildProxyWithStopError(stopError, undefined, containerRemove) + + const shutdown = proxy.shutdown() + await new Promise(resolveImmediate => setImmediate(resolveImmediate)) + + expect(externalNetworkRemove.mock.calls).toHaveLength(0) + expect(internalNetworkRemove.mock.calls).toHaveLength(0) + + reject(removeError) + await expect(shutdown).rejects.toEqual( + new AggregateError([stopError, removeError], 'Failed to clean up proxy') + ) + expect(containerRemove.mock.calls).toHaveLength(1) + expect(externalNetworkRemove.mock.calls).toHaveLength(1) + expect(internalNetworkRemove.mock.calls).toHaveLength(1) + }) +}) diff --git a/dist/main.js b/dist/main.js index 5dda5756f..74b5e5f67 100644 --- a/dist/main.js +++ b/dist/main.js @@ -17050,12 +17050,12 @@ var require_lib = __commonJS({ throw new Error("Client has already been disposed."); } const parsedUrl = new URL(requestUrl); - let info7 = this._prepareRequest(verb, parsedUrl, headers); + let info8 = this._prepareRequest(verb, parsedUrl, headers); const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) ? this._maxRetries + 1 : 1; let numTries = 0; let response; do { - response = yield this.requestRaw(info7, data); + response = yield this.requestRaw(info8, data); if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) { let authenticationHandler; for (const handler of this.handlers) { @@ -17065,7 +17065,7 @@ var require_lib = __commonJS({ } } if (authenticationHandler) { - return authenticationHandler.handleAuthentication(this, info7, data); + return authenticationHandler.handleAuthentication(this, info8, data); } else { return response; } @@ -17088,8 +17088,8 @@ var require_lib = __commonJS({ } } } - info7 = this._prepareRequest(verb, parsedRedirectUrl, headers); - response = yield this.requestRaw(info7, data); + info8 = this._prepareRequest(verb, parsedRedirectUrl, headers); + response = yield this.requestRaw(info8, data); redirectsRemaining--; } if (!response.message.statusCode || !HttpResponseRetryCodes.includes(response.message.statusCode)) { @@ -17118,7 +17118,7 @@ var require_lib = __commonJS({ * @param info * @param data */ - requestRaw(info7, data) { + requestRaw(info8, data) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { function callbackForResult(err, res) { @@ -17130,7 +17130,7 @@ var require_lib = __commonJS({ resolve(res); } } - this.requestRawWithCallback(info7, data, callbackForResult); + this.requestRawWithCallback(info8, data, callbackForResult); }); }); } @@ -17140,12 +17140,12 @@ var require_lib = __commonJS({ * @param data * @param onResult */ - requestRawWithCallback(info7, data, onResult) { + requestRawWithCallback(info8, data, onResult) { if (typeof data === "string") { - if (!info7.options.headers) { - info7.options.headers = {}; + if (!info8.options.headers) { + info8.options.headers = {}; } - info7.options.headers["Content-Length"] = Buffer.byteLength(data, "utf8"); + info8.options.headers["Content-Length"] = Buffer.byteLength(data, "utf8"); } let callbackCalled = false; function handleResult(err, res) { @@ -17154,7 +17154,7 @@ var require_lib = __commonJS({ onResult(err, res); } } - const req = info7.httpModule.request(info7.options, (msg) => { + const req = info8.httpModule.request(info8.options, (msg) => { const res = new HttpClientResponse(msg); handleResult(void 0, res); }); @@ -17166,7 +17166,7 @@ var require_lib = __commonJS({ if (socket) { socket.end(); } - handleResult(new Error(`Request timeout: ${info7.options.path}`)); + handleResult(new Error(`Request timeout: ${info8.options.path}`)); }); req.on("error", function(err) { handleResult(err); @@ -17202,27 +17202,27 @@ var require_lib = __commonJS({ return this._getProxyAgentDispatcher(parsedUrl, proxyUrl); } _prepareRequest(method, requestUrl, headers) { - const info7 = {}; - info7.parsedUrl = requestUrl; - const usingSsl = info7.parsedUrl.protocol === "https:"; - info7.httpModule = usingSsl ? https : http; + const info8 = {}; + info8.parsedUrl = requestUrl; + const usingSsl = info8.parsedUrl.protocol === "https:"; + info8.httpModule = usingSsl ? https : http; const defaultPort = usingSsl ? 443 : 80; - info7.options = {}; - info7.options.host = info7.parsedUrl.hostname; - info7.options.port = info7.parsedUrl.port ? parseInt(info7.parsedUrl.port) : defaultPort; - info7.options.path = (info7.parsedUrl.pathname || "") + (info7.parsedUrl.search || ""); - info7.options.method = method; - info7.options.headers = this._mergeHeaders(headers); + info8.options = {}; + info8.options.host = info8.parsedUrl.hostname; + info8.options.port = info8.parsedUrl.port ? parseInt(info8.parsedUrl.port) : defaultPort; + info8.options.path = (info8.parsedUrl.pathname || "") + (info8.parsedUrl.search || ""); + info8.options.method = method; + info8.options.headers = this._mergeHeaders(headers); if (this.userAgent != null) { - info7.options.headers["user-agent"] = this.userAgent; + info8.options.headers["user-agent"] = this.userAgent; } - info7.options.agent = this._getAgent(info7.parsedUrl); + info8.options.agent = this._getAgent(info8.parsedUrl); if (this.handlers) { for (const handler of this.handlers) { - handler.prepareRequest(info7.options); + handler.prepareRequest(info8.options); } } - return info7; + return info8; } _mergeHeaders(headers) { if (this.requestOptions && this.requestOptions.headers) { @@ -19277,7 +19277,7 @@ var require_core = __commonJS({ exports2.error = error3; exports2.warning = warning5; exports2.notice = notice; - exports2.info = info7; + exports2.info = info8; exports2.startGroup = startGroup2; exports2.endGroup = endGroup2; exports2.group = group; @@ -19374,7 +19374,7 @@ Support boolean input list: \`true | True | TRUE | false | False | FALSE\``); function notice(message, properties = {}) { (0, command_1.issueCommand)("notice", (0, utils_1.toCommandProperties)(properties), message instanceof Error ? message.toString() : message); } - function info7(message) { + function info8(message) { process.stdout.write(message + os.EOL); } function startGroup2(name) { @@ -19870,12 +19870,12 @@ var require_lib2 = __commonJS({ throw new Error("Client has already been disposed."); } const parsedUrl = new URL(requestUrl); - let info7 = this._prepareRequest(verb, parsedUrl, headers); + let info8 = this._prepareRequest(verb, parsedUrl, headers); const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb) ? this._maxRetries + 1 : 1; let numTries = 0; let response; do { - response = yield this.requestRaw(info7, data); + response = yield this.requestRaw(info8, data); if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) { let authenticationHandler; for (const handler of this.handlers) { @@ -19885,7 +19885,7 @@ var require_lib2 = __commonJS({ } } if (authenticationHandler) { - return authenticationHandler.handleAuthentication(this, info7, data); + return authenticationHandler.handleAuthentication(this, info8, data); } else { return response; } @@ -19908,8 +19908,8 @@ var require_lib2 = __commonJS({ } } } - info7 = this._prepareRequest(verb, parsedRedirectUrl, headers); - response = yield this.requestRaw(info7, data); + info8 = this._prepareRequest(verb, parsedRedirectUrl, headers); + response = yield this.requestRaw(info8, data); redirectsRemaining--; } if (!response.message.statusCode || !HttpResponseRetryCodes.includes(response.message.statusCode)) { @@ -19938,7 +19938,7 @@ var require_lib2 = __commonJS({ * @param info * @param data */ - requestRaw(info7, data) { + requestRaw(info8, data) { return __awaiter(this, void 0, void 0, function* () { return new Promise((resolve, reject) => { function callbackForResult(err, res) { @@ -19950,7 +19950,7 @@ var require_lib2 = __commonJS({ resolve(res); } } - this.requestRawWithCallback(info7, data, callbackForResult); + this.requestRawWithCallback(info8, data, callbackForResult); }); }); } @@ -19960,12 +19960,12 @@ var require_lib2 = __commonJS({ * @param data * @param onResult */ - requestRawWithCallback(info7, data, onResult) { + requestRawWithCallback(info8, data, onResult) { if (typeof data === "string") { - if (!info7.options.headers) { - info7.options.headers = {}; + if (!info8.options.headers) { + info8.options.headers = {}; } - info7.options.headers["Content-Length"] = Buffer.byteLength(data, "utf8"); + info8.options.headers["Content-Length"] = Buffer.byteLength(data, "utf8"); } let callbackCalled = false; function handleResult(err, res) { @@ -19974,7 +19974,7 @@ var require_lib2 = __commonJS({ onResult(err, res); } } - const req = info7.httpModule.request(info7.options, (msg) => { + const req = info8.httpModule.request(info8.options, (msg) => { const res = new HttpClientResponse(msg); handleResult(void 0, res); }); @@ -19986,7 +19986,7 @@ var require_lib2 = __commonJS({ if (socket) { socket.end(); } - handleResult(new Error(`Request timeout: ${info7.options.path}`)); + handleResult(new Error(`Request timeout: ${info8.options.path}`)); }); req.on("error", function(err) { handleResult(err); @@ -20022,27 +20022,27 @@ var require_lib2 = __commonJS({ return this._getProxyAgentDispatcher(parsedUrl, proxyUrl); } _prepareRequest(method, requestUrl, headers) { - const info7 = {}; - info7.parsedUrl = requestUrl; - const usingSsl = info7.parsedUrl.protocol === "https:"; - info7.httpModule = usingSsl ? https : http; + const info8 = {}; + info8.parsedUrl = requestUrl; + const usingSsl = info8.parsedUrl.protocol === "https:"; + info8.httpModule = usingSsl ? https : http; const defaultPort = usingSsl ? 443 : 80; - info7.options = {}; - info7.options.host = info7.parsedUrl.hostname; - info7.options.port = info7.parsedUrl.port ? parseInt(info7.parsedUrl.port) : defaultPort; - info7.options.path = (info7.parsedUrl.pathname || "") + (info7.parsedUrl.search || ""); - info7.options.method = method; - info7.options.headers = this._mergeHeaders(headers); + info8.options = {}; + info8.options.host = info8.parsedUrl.hostname; + info8.options.port = info8.parsedUrl.port ? parseInt(info8.parsedUrl.port) : defaultPort; + info8.options.path = (info8.parsedUrl.pathname || "") + (info8.parsedUrl.search || ""); + info8.options.method = method; + info8.options.headers = this._mergeHeaders(headers); if (this.userAgent != null) { - info7.options.headers["user-agent"] = this.userAgent; + info8.options.headers["user-agent"] = this.userAgent; } - info7.options.agent = this._getAgent(info7.parsedUrl); + info8.options.agent = this._getAgent(info8.parsedUrl); if (this.handlers) { for (const handler of this.handlers) { - handler.prepareRequest(info7.options); + handler.prepareRequest(info8.options); } } - return info7; + return info8; } _mergeHeaders(headers) { if (this.requestOptions && this.requestOptions.headers) { @@ -29526,7 +29526,7 @@ var require_crypto = __commonJS({ } var CIPHER_STREAM = 1 << 0; var CIPHER_INFO = (() => { - function info7(sslName, blockLen, keyLen, ivLen, authLen, discardLen, flags) { + function info8(sslName, blockLen, keyLen, ivLen, authLen, discardLen, flags) { return { sslName, blockLen, @@ -29538,25 +29538,25 @@ var require_crypto = __commonJS({ }; } return { - "chacha20-poly1305@openssh.com": info7("chacha20", 8, 64, 0, 16, 0, CIPHER_STREAM), - "aes128-gcm": info7("aes-128-gcm", 16, 16, 12, 16, 0, CIPHER_STREAM), - "aes256-gcm": info7("aes-256-gcm", 16, 32, 12, 16, 0, CIPHER_STREAM), - "aes128-gcm@openssh.com": info7("aes-128-gcm", 16, 16, 12, 16, 0, CIPHER_STREAM), - "aes256-gcm@openssh.com": info7("aes-256-gcm", 16, 32, 12, 16, 0, CIPHER_STREAM), - "aes128-cbc": info7("aes-128-cbc", 16, 16, 0, 0, 0, 0), - "aes192-cbc": info7("aes-192-cbc", 16, 24, 0, 0, 0, 0), - "aes256-cbc": info7("aes-256-cbc", 16, 32, 0, 0, 0, 0), - "rijndael-cbc@lysator.liu.se": info7("aes-256-cbc", 16, 32, 0, 0, 0, 0), - "3des-cbc": info7("des-ede3-cbc", 8, 24, 0, 0, 0, 0), - "blowfish-cbc": info7("bf-cbc", 8, 16, 0, 0, 0, 0), - "idea-cbc": info7("idea-cbc", 8, 16, 0, 0, 0, 0), - "cast128-cbc": info7("cast-cbc", 8, 16, 0, 0, 0, 0), - "aes128-ctr": info7("aes-128-ctr", 16, 16, 16, 0, 0, CIPHER_STREAM), - "aes192-ctr": info7("aes-192-ctr", 16, 24, 16, 0, 0, CIPHER_STREAM), - "aes256-ctr": info7("aes-256-ctr", 16, 32, 16, 0, 0, CIPHER_STREAM), - "3des-ctr": info7("des-ede3", 8, 24, 8, 0, 0, CIPHER_STREAM), - "blowfish-ctr": info7("bf-ecb", 8, 16, 8, 0, 0, CIPHER_STREAM), - "cast128-ctr": info7("cast5-ecb", 8, 16, 8, 0, 0, CIPHER_STREAM), + "chacha20-poly1305@openssh.com": info8("chacha20", 8, 64, 0, 16, 0, CIPHER_STREAM), + "aes128-gcm": info8("aes-128-gcm", 16, 16, 12, 16, 0, CIPHER_STREAM), + "aes256-gcm": info8("aes-256-gcm", 16, 32, 12, 16, 0, CIPHER_STREAM), + "aes128-gcm@openssh.com": info8("aes-128-gcm", 16, 16, 12, 16, 0, CIPHER_STREAM), + "aes256-gcm@openssh.com": info8("aes-256-gcm", 16, 32, 12, 16, 0, CIPHER_STREAM), + "aes128-cbc": info8("aes-128-cbc", 16, 16, 0, 0, 0, 0), + "aes192-cbc": info8("aes-192-cbc", 16, 24, 0, 0, 0, 0), + "aes256-cbc": info8("aes-256-cbc", 16, 32, 0, 0, 0, 0), + "rijndael-cbc@lysator.liu.se": info8("aes-256-cbc", 16, 32, 0, 0, 0, 0), + "3des-cbc": info8("des-ede3-cbc", 8, 24, 0, 0, 0, 0), + "blowfish-cbc": info8("bf-cbc", 8, 16, 0, 0, 0, 0), + "idea-cbc": info8("idea-cbc", 8, 16, 0, 0, 0, 0), + "cast128-cbc": info8("cast-cbc", 8, 16, 0, 0, 0, 0), + "aes128-ctr": info8("aes-128-ctr", 16, 16, 16, 0, 0, CIPHER_STREAM), + "aes192-ctr": info8("aes-192-ctr", 16, 24, 16, 0, 0, CIPHER_STREAM), + "aes256-ctr": info8("aes-256-ctr", 16, 32, 16, 0, 0, CIPHER_STREAM), + "3des-ctr": info8("des-ede3", 8, 24, 8, 0, 0, CIPHER_STREAM), + "blowfish-ctr": info8("bf-ecb", 8, 16, 8, 0, 0, CIPHER_STREAM), + "cast128-ctr": info8("cast5-ecb", 8, 16, 8, 0, 0, CIPHER_STREAM), /* The "arcfour128" algorithm is the RC4 cipher, as described in [SCHNEIER], using a 128-bit key. The first 1536 bytes of keystream generated by the cipher MUST be discarded, and the first byte of the @@ -29564,14 +29564,14 @@ var require_crypto = __commonJS({ keystream. -- http://tools.ietf.org/html/rfc4345#section-4 */ - "arcfour": info7("rc4", 8, 16, 0, 0, 1536, CIPHER_STREAM), - "arcfour128": info7("rc4", 8, 16, 0, 0, 1536, CIPHER_STREAM), - "arcfour256": info7("rc4", 8, 32, 0, 0, 1536, CIPHER_STREAM), - "arcfour512": info7("rc4", 8, 64, 0, 0, 1536, CIPHER_STREAM) + "arcfour": info8("rc4", 8, 16, 0, 0, 1536, CIPHER_STREAM), + "arcfour128": info8("rc4", 8, 16, 0, 0, 1536, CIPHER_STREAM), + "arcfour256": info8("rc4", 8, 32, 0, 0, 1536, CIPHER_STREAM), + "arcfour512": info8("rc4", 8, 64, 0, 0, 1536, CIPHER_STREAM) }; })(); var MAC_INFO = (() => { - function info7(sslName, len, actualLen, isETM) { + function info8(sslName, len, actualLen, isETM) { return { sslName, len, @@ -29580,18 +29580,18 @@ var require_crypto = __commonJS({ }; } return { - "hmac-md5": info7("md5", 16, 16, false), - "hmac-md5-96": info7("md5", 16, 12, false), - "hmac-ripemd160": info7("ripemd160", 20, 20, false), - "hmac-sha1": info7("sha1", 20, 20, false), - "hmac-sha1-etm@openssh.com": info7("sha1", 20, 20, true), - "hmac-sha1-96": info7("sha1", 20, 12, false), - "hmac-sha2-256": info7("sha256", 32, 32, false), - "hmac-sha2-256-etm@openssh.com": info7("sha256", 32, 32, true), - "hmac-sha2-256-96": info7("sha256", 32, 12, false), - "hmac-sha2-512": info7("sha512", 64, 64, false), - "hmac-sha2-512-etm@openssh.com": info7("sha512", 64, 64, true), - "hmac-sha2-512-96": info7("sha512", 64, 12, false) + "hmac-md5": info8("md5", 16, 16, false), + "hmac-md5-96": info8("md5", 16, 12, false), + "hmac-ripemd160": info8("ripemd160", 20, 20, false), + "hmac-sha1": info8("sha1", 20, 20, false), + "hmac-sha1-etm@openssh.com": info8("sha1", 20, 20, true), + "hmac-sha1-96": info8("sha1", 20, 12, false), + "hmac-sha2-256": info8("sha256", 32, 32, false), + "hmac-sha2-256-etm@openssh.com": info8("sha256", 32, 32, true), + "hmac-sha2-256-96": info8("sha256", 32, 12, false), + "hmac-sha2-512": info8("sha512", 64, 64, false), + "hmac-sha2-512-etm@openssh.com": info8("sha512", 64, 64, true), + "hmac-sha2-512-96": info8("sha512", 64, 12, false) }; })(); var NullCipher = class { @@ -40461,7 +40461,7 @@ var require_Channel = __commonJS({ } }; var Channel = class extends DuplexStream { - constructor(client, info7, opts) { + constructor(client, info8, opts) { const streamOpts = { highWaterMark: MAX_WINDOW, allowHalfOpen: !opts || opts && opts.allowHalfOpen !== false, @@ -40471,10 +40471,10 @@ var require_Channel = __commonJS({ this.allowHalfOpen = streamOpts.allowHalfOpen; const server = !!(opts && opts.server); this.server = server; - this.type = info7.type; + this.type = info8.type; this.subtype = void 0; - this.incoming = info7.incoming; - this.outgoing = info7.outgoing; + this.incoming = info8.incoming; + this.outgoing = info8.outgoing; this._callbacks = []; this._client = client; this._hasX11 = false; @@ -40630,16 +40630,16 @@ var require_utils7 = __commonJS({ "use strict"; var { SFTP } = require_SFTP(); var MAX_CHANNEL = 2 ** 32 - 1; - function onChannelOpenFailure(self2, recipient, info7, cb) { + function onChannelOpenFailure(self2, recipient, info8, cb) { self2._chanMgr.remove(recipient); if (typeof cb !== "function") return; let err; - if (info7 instanceof Error) { - err = info7; - } else if (typeof info7 === "object" && info7 !== null) { - err = new Error(`(SSH) Channel open failure: ${info7.description}`); - err.reason = info7.reason; + if (info8 instanceof Error) { + err = info8; + } else if (typeof info8 === "object" && info8 !== null) { + err = new Error(`(SSH) Channel open failure: ${info8.description}`); + err.reason = info8.reason; } else { err = new Error( "(SSH) Channel open failure: server closed channel unexpectedly" @@ -41358,11 +41358,11 @@ var require_client2 = __commonJS({ proto.requestFailure(); } }, - CHANNEL_OPEN: (p, info7) => { - onCHANNEL_OPEN(this, info7); + CHANNEL_OPEN: (p, info8) => { + onCHANNEL_OPEN(this, info8); }, - CHANNEL_OPEN_CONFIRMATION: (p, info7) => { - const channel = this._chanMgr.get(info7.recipient); + CHANNEL_OPEN_CONFIRMATION: (p, info8) => { + const channel = this._chanMgr.get(info8.recipient); if (typeof channel !== "function") return; const isSFTP = channel.type === "sftp"; @@ -41370,28 +41370,28 @@ var require_client2 = __commonJS({ const chanInfo = { type, incoming: { - id: info7.recipient, + id: info8.recipient, window: MAX_WINDOW, packetSize: PACKET_SIZE, state: "open" }, outgoing: { - id: info7.sender, - window: info7.window, - packetSize: info7.packetSize, + id: info8.sender, + window: info8.window, + packetSize: info8.packetSize, state: "open" } }; const instance = isSFTP ? new SFTP(this, chanInfo, { debug: debug2 }) : new Channel(this, chanInfo); - this._chanMgr.update(info7.recipient, instance); + this._chanMgr.update(info8.recipient, instance); channel(void 0, instance); }, CHANNEL_OPEN_FAILURE: (p, recipient, reason, description) => { const channel = this._chanMgr.get(recipient); if (typeof channel !== "function") return; - const info7 = { reason, description }; - onChannelOpenFailure(this, recipient, info7, channel); + const info8 = { reason, description }; + onChannelOpenFailure(this, recipient, info8, channel); }, CHANNEL_DATA: (p, recipient, data) => { const channel = this._chanMgr.get(recipient); @@ -42488,12 +42488,12 @@ var require_client2 = __commonJS({ }); chan._client._protocol.subsystem(chan.outgoing.id, name, true); } - function onCHANNEL_OPEN(self2, info7) { + function onCHANNEL_OPEN(self2, info8) { let localChan = -1; let reason; const accept = () => { const chanInfo = { - type: info7.type, + type: info8.type, incoming: { id: localChan, window: MAX_WINDOW, @@ -42501,16 +42501,16 @@ var require_client2 = __commonJS({ state: "open" }, outgoing: { - id: info7.sender, - window: info7.window, - packetSize: info7.packetSize, + id: info8.sender, + window: info8.window, + packetSize: info8.packetSize, state: "open" } }; const stream2 = new Channel(self2, chanInfo); self2._chanMgr.update(localChan, stream2); self2._protocol.channelOpenConfirm( - info7.sender, + info8.sender, localChan, MAX_WINDOW, PACKET_SIZE @@ -42526,7 +42526,7 @@ var require_client2 = __commonJS({ } if (localChan !== -1) self2._chanMgr.remove(localChan); - self2._protocol.channelOpenFail(info7.sender, reason, ""); + self2._protocol.channelOpenFail(info8.sender, reason, ""); }; const reserveChannel = () => { localChan = self2._chanMgr.add(); @@ -42540,8 +42540,8 @@ var require_client2 = __commonJS({ } return localChan !== -1; }; - const data = info7.data; - switch (info7.type) { + const data = info8.data; + switch (info8.type) { case "forwarded-tcpip": { const val = self2._forwarding[`${data.destIP}:${data.destPort}`]; if (val !== void 0 && reserveChannel()) { @@ -42579,7 +42579,7 @@ var require_client2 = __commonJS({ reason = CHANNEL_OPEN_FAILURE.UNKNOWN_CHANNEL_TYPE; if (self2.config.debug) { self2.config.debug( - `Client: Automatic rejection of unsupported incoming channel open type: ${info7.type}` + `Client: Automatic rejection of unsupported incoming channel open type: ${info8.type}` ); } } @@ -42587,7 +42587,7 @@ var require_client2 = __commonJS({ reason = CHANNEL_OPEN_FAILURE.ADMINISTRATIVELY_PROHIBITED; if (self2.config.debug) { self2.config.debug( - "Client: Automatic rejection of unexpected incoming channel open for: " + info7.type + "Client: Automatic rejection of unexpected incoming channel open for: " + info8.type ); } } @@ -42944,7 +42944,7 @@ var require_server = __commonJS({ } }; var Session = class extends EventEmitter { - constructor(client, info7, localChan) { + constructor(client, info8, localChan) { super(); this.type = "session"; this.subtype = void 0; @@ -42960,9 +42960,9 @@ var require_server = __commonJS({ state: "open" }, outgoing: { - id: info7.sender, - window: info7.window, - packetSize: info7.packetSize, + id: info8.sender, + window: info8.window, + packetSize: info8.packetSize, state: "open" } }; @@ -43186,13 +43186,13 @@ var require_server = __commonJS({ }, onHeader: (header) => { this.removeListener("error", onClientPreHeaderError); - const info7 = { + const info8 = { ip: socket.remoteAddress, family: socket.remoteFamily, port: socket.remotePort, header }; - if (!server.emit("connection", this, info7)) { + if (!server.emit("connection", this, info8)) { proto.disconnect(DISCONNECT_REASON.BY_APPLICATION); socket.end(); return; @@ -43221,10 +43221,10 @@ var require_server = __commonJS({ } socket.end(); }, - CHANNEL_OPEN: (p, info7) => { - if (info7.type === "session" && this.noMoreSessions || !this.authenticated) { + CHANNEL_OPEN: (p, info8) => { + if (info8.type === "session" && this.noMoreSessions || !this.authenticated) { const reasonCode = CHANNEL_OPEN_FAILURE.ADMINISTRATIVELY_PROHIBITED; - return proto.channelOpenFail(info7.sender, reasonCode); + return proto.channelOpenFail(info8.sender, reasonCode); } let localChan = -1; let reason; @@ -43242,7 +43242,7 @@ var require_server = __commonJS({ } if (localChan !== -1) this._chanMgr.remove(localChan); - proto.channelOpenFail(info7.sender, reason, ""); + proto.channelOpenFail(info8.sender, reason, ""); }; const reserveChannel = () => { localChan = this._chanMgr.add(); @@ -43254,18 +43254,18 @@ var require_server = __commonJS({ } return localChan !== -1; }; - const data = info7.data; - switch (info7.type) { + const data = info8.data; + switch (info8.type) { case "session": if (listenerCount(this, "session") && reserveChannel()) { accept = () => { if (replied) return; replied = true; - const instance = new Session(this, info7, localChan); + const instance = new Session(this, info8, localChan); this._chanMgr.update(localChan, instance); proto.channelOpenConfirm( - info7.sender, + info8.sender, localChan, MAX_WINDOW, PACKET_SIZE @@ -43291,16 +43291,16 @@ var require_server = __commonJS({ state: "open" }, outgoing: { - id: info7.sender, - window: info7.window, - packetSize: info7.packetSize, + id: info8.sender, + window: info8.window, + packetSize: info8.packetSize, state: "open" } }; const stream2 = new Channel(this, chanInfo, { server: true }); this._chanMgr.update(localChan, stream2); proto.channelOpenConfirm( - info7.sender, + info8.sender, localChan, MAX_WINDOW, PACKET_SIZE @@ -43326,16 +43326,16 @@ var require_server = __commonJS({ state: "open" }, outgoing: { - id: info7.sender, - window: info7.window, - packetSize: info7.packetSize, + id: info8.sender, + window: info8.window, + packetSize: info8.packetSize, state: "open" } }; const stream2 = new Channel(this, chanInfo, { server: true }); this._chanMgr.update(localChan, stream2); proto.channelOpenConfirm( - info7.sender, + info8.sender, localChan, MAX_WINDOW, PACKET_SIZE @@ -43349,46 +43349,46 @@ var require_server = __commonJS({ default: reason = CHANNEL_OPEN_FAILURE.UNKNOWN_CHANNEL_TYPE; if (debug2) { - debug2(`Automatic rejection of unsupported incoming channel open type: ${info7.type}`); + debug2(`Automatic rejection of unsupported incoming channel open type: ${info8.type}`); } } if (reason === void 0) { reason = CHANNEL_OPEN_FAILURE.ADMINISTRATIVELY_PROHIBITED; if (debug2) { - debug2(`Automatic rejection of unexpected incoming channel open for: ${info7.type}`); + debug2(`Automatic rejection of unexpected incoming channel open for: ${info8.type}`); } } reject(); }, - CHANNEL_OPEN_CONFIRMATION: (p, info7) => { - const channel = this._chanMgr.get(info7.recipient); + CHANNEL_OPEN_CONFIRMATION: (p, info8) => { + const channel = this._chanMgr.get(info8.recipient); if (typeof channel !== "function") return; const chanInfo = { type: channel.type, incoming: { - id: info7.recipient, + id: info8.recipient, window: MAX_WINDOW, packetSize: PACKET_SIZE, state: "open" }, outgoing: { - id: info7.sender, - window: info7.window, - packetSize: info7.packetSize, + id: info8.sender, + window: info8.window, + packetSize: info8.packetSize, state: "open" } }; const instance = new Channel(this, chanInfo, { server: true }); - this._chanMgr.update(info7.recipient, instance); + this._chanMgr.update(info8.recipient, instance); channel(void 0, instance); }, CHANNEL_OPEN_FAILURE: (p, recipient, reason, description) => { const channel = this._chanMgr.get(recipient); if (typeof channel !== "function") return; - const info7 = { reason, description }; - onChannelOpenFailure(this, recipient, info7, channel); + const info8 = { reason, description }; + onChannelOpenFailure(this, recipient, info8, channel); }, CHANNEL_DATA: (p, recipient, data) => { let channel = this._chanMgr.get(recipient); @@ -46111,13 +46111,13 @@ var require_from = __commonJS({ "use strict"; function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { - var info7 = gen[key](arg); - var value = info7.value; + var info8 = gen[key](arg); + var value = info8.value; } catch (error3) { reject(error3); return; } - if (info7.done) { + if (info8.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); @@ -98894,7 +98894,7 @@ __export(main_exports, { run: () => run }); module.exports = __toCommonJS(main_exports); -var core7 = __toESM(require_core()); +var core8 = __toESM(require_core()); var github = __toESM(require_github()); var httpClient2 = __toESM(require_lib()); @@ -99317,6 +99317,7 @@ function fromWorkflowInputs(ctx) { } // src/updater.ts +var core7 = __toESM(require_core()); var import_dockerode2 = __toESM(require_docker()); // src/container-service.ts @@ -99585,9 +99586,34 @@ var ProxyBuilder = class { waitUntilReady, cert, shutdown: async () => { - await container.stop(); - await container.remove(); - await Promise.all([externalNetwork.remove(), internalNetwork.remove()]); + const cleanupErrors = []; + try { + await container.stop(); + } catch (error3) { + if (typeof error3 !== "object" || error3 === null || !("statusCode" in error3) || error3.statusCode !== 304) { + cleanupErrors.push(error3); + } + } + try { + await container.remove(); + } catch (error3) { + cleanupErrors.push(error3); + } + const networkCleanupResults = await Promise.allSettled([ + externalNetwork.remove(), + internalNetwork.remove() + ]); + for (const result of networkCleanupResults) { + if (result.status === "rejected") { + cleanupErrors.push(result.reason); + } + } + if (cleanupErrors.length === 1) { + throw cleanupErrors[0]; + } + if (cleanupErrors.length > 1) { + throw new AggregateError(cleanupErrors, "Failed to clean up proxy"); + } } }; } @@ -99825,10 +99851,21 @@ var Updater = class { try { await proxy.waitUntilReady(); await this.runUpdate(proxy); - return true; - } finally { - await this.cleanup(proxy); + } catch (error3) { + try { + await this.cleanup(proxy); + } catch (cleanupError) { + const cleanupErrors = cleanupError instanceof AggregateError ? cleanupError.errors : [cleanupError]; + for (const cleanupFailure of cleanupErrors) { + core7.info( + `Failed to clean up proxy after update failure: ${cleanupFailure}` + ); + } + } + throw error3; } + await this.cleanup(proxy); + return true; } generateCredentialsMetadata() { const unique = /* @__PURE__ */ new Set(); @@ -99957,21 +99994,21 @@ async function run(context2) { if (!jobToken) { const errorMessage = "Github Dependabot job token is not set"; botSay(`finished: ${errorMessage}`); - core7.setFailed(errorMessage); + core8.setFailed(errorMessage); return; } if (!credentialsToken) { const errorMessage = "Github Dependabot credentials token is not set"; botSay(`finished: ${errorMessage}`); - core7.setFailed(errorMessage); + core8.setFailed(errorMessage); return; } jobId = params.jobId; - core7.setSecret(jobToken); - core7.setSecret(credentialsToken); + core8.setSecret(jobToken); + core8.setSecret(credentialsToken); const client = new httpClient2.HttpClient("github/dependabot-action"); const apiClient = new ApiClient(client, params, jobToken, credentialsToken); - core7.info("Fetching job details"); + core8.info("Fetching job details"); const details = await apiClient.getJobDetails(); let updaterImage = params.updaterImage || updaterImageName(details["package-manager"]); let proxyImage = PROXY_IMAGE_NAME; @@ -99982,7 +100019,7 @@ async function run(context2) { ...additionalTags }); } catch (error3) { - core7.warning( + core8.warning( `Metric sending failed for ${name}: ${error3.message}` ); } @@ -99993,10 +100030,10 @@ async function run(context2) { credentials.push(...registryCredentials); const packagesCred = getPackagesCredential(details, context2.actor); if (packagesCred !== null) { - core7.info("Adding GitHub Packages credential"); + core8.info("Adding GitHub Packages credential"); credentials.push(packagesCred); } - core7.startGroup("Pulling updater images"); + core8.startGroup("Pulling updater images"); let imagesPulled = false; let pullError = new Error("No image source was configured"); const experiments = details?.experiments || {}; @@ -100012,7 +100049,7 @@ async function run(context2) { } } if (!imagesPulled && experiments[FEATURE_PULL_FROM_AZURE]) { - core7.warning("Primary image pull failed, attempting fallback"); + core8.warning("Primary image pull failed, attempting fallback"); updaterImage = `${FALLBACK_CONTAINER_REGISTRY}/${updaterImage}`; proxyImage = `${FALLBACK_CONTAINER_REGISTRY}/${proxyImage}`; try { @@ -100034,9 +100071,9 @@ async function run(context2) { ); return; } - core7.endGroup(); + core8.endGroup(); try { - core7.info("Starting update process"); + core8.info("Starting update process"); const updater = new Updater( updaterImage, proxyImage, @@ -100091,12 +100128,12 @@ function getPackagesCredential(jobDetails, actor) { } const githubToken = process.env.GITHUB_TOKEN; if (!githubToken) { - core7.warning( + core8.warning( "GITHUB_TOKEN is not set; cannot create GitHub Packages credential" ); return null; } - core7.setSecret(githubToken); + core8.setSecret(githubToken); let credential = null; switch (jobDetails["package-manager"]) { case "bundler": @@ -100207,13 +100244,13 @@ async function failJob(apiClient, message, error3, errorType = "actions_workflow botSay("finished: error reported to Dependabot"); } function botSay(message) { - core7.info(`\u{1F916} ~ ${message} ~`); + core8.info(`\u{1F916} ~ ${message} ~`); } function setFailed2(message, error3) { if (jobId) { message = [message, error3, dependabotJobHelp()].filter(Boolean).join("\n\n"); } - core7.setFailed(message); + core8.setFailed(message); } function dependabotJobHelp() { if (jobId) { @@ -100259,7 +100296,7 @@ function credentialsFromEnv() { for (const e of parsed) { for (const key of Object.keys(e)) { if (!nonSecrets.includes(key)) { - core7.setSecret(e[key]); + core8.setSecret(e[key]); } } } diff --git a/src/proxy.ts b/src/proxy.ts index 5d1b6e956..af8f8cda6 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -159,9 +159,42 @@ export class ProxyBuilder { waitUntilReady, cert, shutdown: async () => { - await container.stop() - await container.remove() - await Promise.all([externalNetwork.remove(), internalNetwork.remove()]) + const cleanupErrors: unknown[] = [] + try { + await container.stop() + } catch (error) { + if ( + typeof error !== 'object' || + error === null || + !('statusCode' in error) || + error.statusCode !== 304 + ) { + cleanupErrors.push(error) + } + } + + try { + await container.remove() + } catch (error) { + cleanupErrors.push(error) + } + + const networkCleanupResults = await Promise.allSettled([ + externalNetwork.remove(), + internalNetwork.remove() + ]) + for (const result of networkCleanupResults) { + if (result.status === 'rejected') { + cleanupErrors.push(result.reason) + } + } + + if (cleanupErrors.length === 1) { + throw cleanupErrors[0] + } + if (cleanupErrors.length > 1) { + throw new AggregateError(cleanupErrors, 'Failed to clean up proxy') + } } } } diff --git a/src/updater.ts b/src/updater.ts index 5598e3d09..71f3764f0 100644 --- a/src/updater.ts +++ b/src/updater.ts @@ -1,3 +1,4 @@ +import * as core from '@actions/core' import Docker, {Container} from 'dockerode' import {JobDetails, ApiClient, Credential} from './api-client' import {ContainerService} from './container-service' @@ -45,10 +46,25 @@ export class Updater { try { await proxy.waitUntilReady() await this.runUpdate(proxy) - return true - } finally { - await this.cleanup(proxy) + } catch (error) { + try { + await this.cleanup(proxy) + } catch (cleanupError) { + const cleanupErrors = + cleanupError instanceof AggregateError + ? cleanupError.errors + : [cleanupError] + for (const cleanupFailure of cleanupErrors) { + core.info( + `Failed to clean up proxy after update failure: ${cleanupFailure}` + ) + } + } + throw error } + + await this.cleanup(proxy) + return true } private generateCredentialsMetadata(): Credential[] { From 45f2998f4adc3b18aadaff97b76e7439db2658d8 Mon Sep 17 00:00:00 2001 From: Jurre Stender Date: Wed, 12 Aug 2026 16:14:17 +0200 Subject: [PATCH 3/3] Rebuild distribution bundles Regenerate the checked-in action bundles from a clean dependency install. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: c7891dcd-6a84-4edc-a6e7-47e7a4b9c37d --- dist/cleanup.js | 8 +++++++- dist/main.js | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/dist/cleanup.js b/dist/cleanup.js index 468242832..969ce63b8 100644 --- a/dist/cleanup.js +++ b/dist/cleanup.js @@ -24295,6 +24295,12 @@ var require_utils4 = __commonJS({ } }); +// node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node +var require_sshcrypto = __commonJS({ + "node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node"() { + } +}); + // node_modules/ssh2/lib/protocol/crypto/poly1305.js var require_poly1305 = __commonJS({ "node_modules/ssh2/lib/protocol/crypto/poly1305.js"(exports2, module2) { @@ -24749,7 +24755,7 @@ var require_crypto = __commonJS({ var ChaChaPolyDecipher; var GenericDecipher; try { - binding = require("./crypto/build/Release/sshcrypto.node"); + binding = require_sshcrypto(); ({ AESGCMCipher, ChaChaPolyCipher, diff --git a/dist/main.js b/dist/main.js index 74b5e5f67..a253d422c 100644 --- a/dist/main.js +++ b/dist/main.js @@ -29059,6 +29059,12 @@ var require_utils6 = __commonJS({ } }); +// node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node +var require_sshcrypto = __commonJS({ + "node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node"() { + } +}); + // node_modules/ssh2/lib/protocol/crypto/poly1305.js var require_poly1305 = __commonJS({ "node_modules/ssh2/lib/protocol/crypto/poly1305.js"(exports2, module2) { @@ -29513,7 +29519,7 @@ var require_crypto = __commonJS({ var ChaChaPolyDecipher; var GenericDecipher; try { - binding = require("./crypto/build/Release/sshcrypto.node"); + binding = require_sshcrypto(); ({ AESGCMCipher, ChaChaPolyCipher,