@paklo/runner 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,770 @@
1
+ import { t as logger } from "./logger-Bekleunx.js";
2
+ import { DependabotCredentialSchema, DependabotJobConfigSchema } from "@paklo/core/dependabot";
3
+ import { HEADER_NAME_AUTHORIZATION, HttpRequestError, InnerApiClient, isErrorTemporaryFailure } from "@paklo/core/http";
4
+ import Docker from "dockerode";
5
+ import { pack } from "tar-stream";
6
+ import stream, { Readable } from "node:stream";
7
+ import crypto from "node:crypto";
8
+ import os from "node:os";
9
+ import { readFile } from "node:fs/promises";
10
+
11
+ //#region src/api-client.ts
12
+ var JobDetailsFetchingError = class extends Error {};
13
+ var CredentialFetchingError = class extends Error {};
14
+ var ApiClient = class {
15
+ jobToken;
16
+ constructor(client, params, jobToken, credentialsToken, secretMasker) {
17
+ this.client = client;
18
+ this.params = params;
19
+ this.credentialsToken = credentialsToken;
20
+ this.secretMasker = secretMasker;
21
+ this.jobToken = jobToken;
22
+ }
23
+ UnknownSha = { "base-commit-sha": "unknown" };
24
+ getJobToken() {
25
+ return this.jobToken;
26
+ }
27
+ async getJobDetails() {
28
+ try {
29
+ const res = await this.getWithRetry(`/update_jobs/${this.params.jobId}/details`, this.jobToken, { schema: DependabotJobConfigSchema });
30
+ if (res.status !== 200) throw new JobDetailsFetchingError(`fetching job details: unexpected status code: ${res.status}: ${JSON.stringify(res.error)}`);
31
+ if (!res.data) throw new JobDetailsFetchingError(`fetching job details: missing response`);
32
+ return res.data;
33
+ } catch (error) {
34
+ if (error instanceof JobDetailsFetchingError) throw error;
35
+ else if (error instanceof HttpRequestError) throw new JobDetailsFetchingError(`fetching job details: unexpected status code: ${error.code}: ${error.message}`);
36
+ else if (error instanceof Error) throw new JobDetailsFetchingError(`fetching job details: ${error.name}: ${error.message}`);
37
+ throw error;
38
+ }
39
+ }
40
+ async getCredentials() {
41
+ try {
42
+ const res = await this.getWithRetry(`/update_jobs/${this.params.jobId}/credentials`, this.credentialsToken, { schema: DependabotCredentialSchema.array() });
43
+ if (res.status !== 200) throw new CredentialFetchingError(`fetching credentials: unexpected status code: ${res.status}: ${JSON.stringify(res.error)}`);
44
+ if (!res.data) throw new CredentialFetchingError(`fetching credentials: missing response`);
45
+ for (const credential of res.data) {
46
+ if (credential.password) this.secretMasker(credential.password);
47
+ if (credential.token) this.secretMasker(credential.token);
48
+ if (credential["auth-key"]) this.secretMasker(credential["auth-key"]);
49
+ }
50
+ return res.data;
51
+ } catch (error) {
52
+ if (error instanceof CredentialFetchingError) throw error;
53
+ else if (error instanceof HttpRequestError) throw new CredentialFetchingError(`fetching credentials: unexpected status code: ${error.code}: ${error.message}`);
54
+ else if (error instanceof Error) throw new CredentialFetchingError(`fetching credentials: ${error.name}: ${error.message}`);
55
+ throw error;
56
+ }
57
+ }
58
+ async reportJobError(error) {
59
+ const res = await this.client.post(`/update_jobs/${this.params.jobId}/record_update_job_error`, {
60
+ payload: error,
61
+ headers: { [HEADER_NAME_AUTHORIZATION]: this.jobToken }
62
+ });
63
+ if (res.status !== 204) throw new Error(`Unexpected status code: ${res.status}`);
64
+ }
65
+ async markJobAsProcessed() {
66
+ const res = await this.client.patch(`/update_jobs/${this.params.jobId}/mark_as_processed`, {
67
+ payload: this.UnknownSha,
68
+ headers: { [HEADER_NAME_AUTHORIZATION]: this.jobToken }
69
+ });
70
+ if (res.status !== 204) throw new Error(`Unexpected status code: ${res.status}`);
71
+ }
72
+ async sendMetrics(name, metricType, value, additionalTags = {}) {
73
+ try {
74
+ await this.reportMetrics([{
75
+ metric: `dependabot.action.${name}`,
76
+ type: metricType,
77
+ value,
78
+ tags: additionalTags
79
+ }]);
80
+ logger.info(`Successfully sent metric (dependabot.action.${name}) to remote API endpoint`);
81
+ } catch (error) {
82
+ logger.warn(`Metrics reporting failed: ${error.message}`);
83
+ }
84
+ }
85
+ async reportMetrics(metrics) {
86
+ const res = await this.client.post(`/update_jobs/${this.params.jobId}/record_metrics`, {
87
+ payload: { data: metrics },
88
+ headers: { [HEADER_NAME_AUTHORIZATION]: this.jobToken }
89
+ });
90
+ if (res.status !== 204) throw new Error(`Unexpected status code: ${res.status}`);
91
+ }
92
+ async getWithRetry(url, token, options) {
93
+ let attempt = 1;
94
+ const delayMs = 1e3 * 2 ** attempt;
95
+ const execute = async () => {
96
+ try {
97
+ const res = await this.client.get(url, {
98
+ headers: { Authorization: token },
99
+ ...options
100
+ });
101
+ const { status, statusText } = res;
102
+ if (status < 200 || status > 299) throw new HttpRequestError(`HTTP GET '${url}' failed: ${status} ${statusText}`, status);
103
+ return res;
104
+ } catch (e) {
105
+ const error = e;
106
+ if (isErrorTemporaryFailure(error)) {
107
+ if (attempt >= 3) throw error;
108
+ logger.warn(`Retrying failed request in ${delayMs}ms...`);
109
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
110
+ attempt++;
111
+ return execute();
112
+ }
113
+ throw error;
114
+ }
115
+ };
116
+ return execute();
117
+ }
118
+ };
119
+
120
+ //#endregion
121
+ //#region ../../dependabot-action/docker/containers.json
122
+ var proxy = "ghcr.io/github/dependabot-update-job-proxy/dependabot-update-job-proxy:v2.0.20251015175503@sha256:d40c689e947e78ecdaccb3da1d070a458b7f1d1cfb3052cf5751e43583d89560";
123
+ var containers_default = {
124
+ proxy,
125
+ bundler: "ghcr.io/dependabot/dependabot-updater-bundler:v2.0.20250916161401@sha256:cb1b48a4e2862bd9a2ebb1bb7f2eb1b28bd0099060925951618e07a96c191e5c",
126
+ cargo: "ghcr.io/dependabot/dependabot-updater-cargo:v2.0.20250916161401@sha256:dc8823384d8fd864f8b7867b553df3489b658b236e9bbfad49606c819d9bc450",
127
+ composer: "ghcr.io/dependabot/dependabot-updater-composer:v2.0.20250916161401@sha256:9d9304ed225f1ed0614d55d1c10398c93476cff6e295bb912816ed323d5ad0d0",
128
+ pub: "ghcr.io/dependabot/dependabot-updater-pub:v2.0.20250916161401@sha256:2955d6f1d77cc9ca12cf8e2fa5c919b3d5a79c403a2aad94550f2955e233a0c4",
129
+ docker: "ghcr.io/dependabot/dependabot-updater-docker:v2.0.20250916161401@sha256:411a5eb299308037ec396b51b0ecdb7f4ee3deeeb392202becc1a333a6dbab25",
130
+ elm: "ghcr.io/dependabot/dependabot-updater-elm:v2.0.20250916161401@sha256:67924991be2870fc9cf26bcb031146a46e4b9812173000b4e45acdb929fd0085",
131
+ github_actions: "ghcr.io/dependabot/dependabot-updater-github-actions:v2.0.20250916161401@sha256:675a96888497d8b47328ede0f2163722a90b901c7116e719830573c64b8c2465",
132
+ submodules: "ghcr.io/dependabot/dependabot-updater-gitsubmodule:v2.0.20250916161401@sha256:deae36a972cfc284dde6e8b6a923dbb81bc794b8c8a67ea652dcf7e71caab710",
133
+ go_modules: "ghcr.io/dependabot/dependabot-updater-gomod:v2.0.20250916161401@sha256:21bbf01be40bd53ccc6efd137aa309a1b895dcfab4eb62d85611d94806db8b58",
134
+ gradle: "ghcr.io/dependabot/dependabot-updater-gradle:v2.0.20250916161401@sha256:7482ff1cb4cf222a2a96741c8c506609eedc93f0e4cd7c38fe73e4a804413134",
135
+ maven: "ghcr.io/dependabot/dependabot-updater-maven:v2.0.20250916161401@sha256:b444c349e9ae8ec3bec9eef411ea830e8fa168c9ee0397e8c86eb140ea933167",
136
+ hex: "ghcr.io/dependabot/dependabot-updater-mix:v2.0.20250916161401@sha256:32b74d14082a0b89c9d8bcdde92a3d2b18f5798b6d9bcf2080855373c3f45c1f",
137
+ nuget: "ghcr.io/dependabot/dependabot-updater-nuget:v2.0.20250916161401@sha256:9fb516772dffa7a014c20a8dde909ccb25a323d7039a58346401a4500ce64657",
138
+ npm_and_yarn: "ghcr.io/dependabot/dependabot-updater-npm:v2.0.20250916161401@sha256:7d13ce84d26210659dbb5fd4b9c0d72b34786ca02063737cba5d228ad55af273",
139
+ pip: "ghcr.io/dependabot/dependabot-updater-pip:v2.0.20250916161401@sha256:a05999d53df5ea7141aafde806bd7e1a25dc23087528aea0b482a86363956937",
140
+ rust_toolchain: "ghcr.io/dependabot/dependabot-updater-rust-toolchain:v2.0.20250916161401@sha256:1688181ea18f1736ff80e6fe9bb17de3508b3ea890c20493e82cd9a68f6a5387",
141
+ swift: "ghcr.io/dependabot/dependabot-updater-swift:v2.0.20250916161401@sha256:622971aba7877711401d021bc0ecfeb98cbd39767b0c45dcf150b86536968743",
142
+ terraform: "ghcr.io/dependabot/dependabot-updater-terraform:v2.0.20250916161401@sha256:82c7cef04f54a20a6c47426f9ff48b7767757e715db5fa0a1b0357ced50bbf6c",
143
+ devcontainers: "ghcr.io/dependabot/dependabot-updater-devcontainers:v2.0.20250916161401@sha256:5691a9bec5b9c91879318323ed53d22d7c73c540376bb2e0d74704e9651e9897",
144
+ dotnet_sdk: "ghcr.io/dependabot/dependabot-updater-dotnet-sdk:v2.0.20250916161401@sha256:10febb67dbcdf4e50985412d65e1fd4a364f7402e184cfcd67b63d295a6c4e80",
145
+ bun: "ghcr.io/dependabot/dependabot-updater-bun:v2.0.20250915234339@sha256:fba1acd818ca0101f71ba34f04c8b7c36abf8e63de5feb05037b42f6406950fa",
146
+ docker_compose: "ghcr.io/dependabot/dependabot-updater-docker-compose:v2.0.20250916161401@sha256:a0fc653bedf0e600d85a3f7bc0eee7ec7bee99f6875d4faf6e30e2c69ea36dbe",
147
+ uv: "ghcr.io/dependabot/dependabot-updater-uv:v2.0.20250916161401@sha256:441fe91d1ed3ba9c148abe5dc3a3d83f12805326da5c037d76b86695c247c1cb",
148
+ vcpkg: "ghcr.io/dependabot/dependabot-updater-vcpkg:v2.0.20250916161401@sha256:40355d74ad784932730577475faee9f21ef863c3cec7b7d2817cc5621f3b1dd7",
149
+ helm: "ghcr.io/dependabot/dependabot-updater-helm:v2.0.20250916161401@sha256:42fe3e7a6bac84271dec7ec41ac0067ff7d1cffb8e5f63dbe5eec849b5bc433b"
150
+ };
151
+
152
+ //#endregion
153
+ //#region src/docker-tags.ts
154
+ const PROXY_IMAGE_NAME = proxy;
155
+ function updaterImageName(packageManager) {
156
+ return containers_default[packageManager];
157
+ }
158
+ const updaterRegex = /ghcr.io\/dependabot\/dependabot-updater-([\w+])/;
159
+ function updaterImages() {
160
+ return Object.values(containers_default).filter((image) => image.match(updaterRegex));
161
+ }
162
+ const imageNamePattern = "^(?<repository>(([a-zA-Z0-9._-]+([:[0-9]+[^/]))?([a-zA-Z0-9._/-]+)?))(:[a-zA-Z0-9._/-]+)?(?<digest>@sha256:[a-zA-Z0-9]{64})?$";
163
+ function repositoryName(imageName) {
164
+ const match = imageName.match(imageNamePattern);
165
+ if (match?.groups) return match.groups.repository;
166
+ else throw Error("invalid image name");
167
+ }
168
+ function hasDigest(imageName) {
169
+ const match = imageName.match(imageNamePattern);
170
+ if (match?.groups) {
171
+ if (match?.groups.digest) return true;
172
+ return false;
173
+ } else throw Error("invalid image name");
174
+ }
175
+ function digestName(imageName) {
176
+ const match = imageName.match(imageNamePattern);
177
+ if (match?.groups) return match.groups.repository + match.groups.digest;
178
+ else throw Error("invalid image name");
179
+ }
180
+
181
+ //#endregion
182
+ //#region src/utils.ts
183
+ const outStream = (prefix) => {
184
+ return new stream.Writable({ write(chunk, _, next) {
185
+ process.stderr.write(`${prefix} | ${chunk.toString()}`);
186
+ next();
187
+ } });
188
+ };
189
+ const errStream = (prefix) => {
190
+ return new stream.Writable({ write(chunk, _, next) {
191
+ process.stderr.write(`${prefix} | ${chunk.toString()}`);
192
+ next();
193
+ } });
194
+ };
195
+
196
+ //#endregion
197
+ //#region src/container-service.ts
198
+ var ContainerRuntimeError = class extends Error {};
199
+ const RWX_ALL = 511;
200
+ const ContainerService = {
201
+ async storeInput(name, path, container, input) {
202
+ const tar = pack();
203
+ tar.entry({
204
+ name,
205
+ mode: RWX_ALL
206
+ }, JSON.stringify(input));
207
+ tar.finalize();
208
+ await container.putArchive(tar, { path });
209
+ },
210
+ async storeCert(name, path, container, cert) {
211
+ const tar = pack();
212
+ tar.entry({ name }, cert);
213
+ tar.finalize();
214
+ await container.putArchive(tar, { path });
215
+ },
216
+ async run(container, command) {
217
+ try {
218
+ await container.start();
219
+ logger.info(`Started container ${container.id}`);
220
+ if ((await container.inspect()).Config?.Env?.some((env) => env.startsWith("DEPENDABOT_JOB_ID="))) {
221
+ await this.execCommand(container, ["/usr/sbin/update-ca-certificates"], "root");
222
+ const dependabotCommands = ["mkdir -p /home/dependabot/dependabot-updater/output", "$DEPENDABOT_HOME/dependabot-updater/bin/run fetch_files"];
223
+ if (command === "graph") dependabotCommands.push("$DEPENDABOT_HOME/dependabot-updater/bin/run update_graph");
224
+ else dependabotCommands.push("$DEPENDABOT_HOME/dependabot-updater/bin/run update_files");
225
+ for (const cmd of dependabotCommands) await this.execCommand(container, [
226
+ "/bin/sh",
227
+ "-c",
228
+ cmd
229
+ ], "dependabot");
230
+ } else {
231
+ const outcome = await container.wait();
232
+ if (outcome.StatusCode !== 0) throw new Error(`Container exited with code ${outcome.StatusCode}`);
233
+ }
234
+ return true;
235
+ } catch (error) {
236
+ logger.info(`Failure running container ${container.id}: ${error}`);
237
+ throw new ContainerRuntimeError("The updater encountered one or more errors.");
238
+ } finally {
239
+ try {
240
+ await container.remove({
241
+ v: true,
242
+ force: true
243
+ });
244
+ logger.info(`Cleaned up container ${container.id}`);
245
+ } catch (error) {
246
+ logger.info(`Failed to clean up container ${container.id}: ${error}`);
247
+ }
248
+ }
249
+ },
250
+ async execCommand(container, cmd, user) {
251
+ const exec = await container.exec({
252
+ Cmd: cmd,
253
+ User: user,
254
+ AttachStdout: true,
255
+ AttachStderr: true
256
+ });
257
+ const stream$1 = await exec.start({});
258
+ await new Promise((resolve, reject) => {
259
+ container.modem.demuxStream(stream$1, outStream("updater"), errStream("updater"));
260
+ stream$1.on("end", () => {
261
+ resolve();
262
+ });
263
+ stream$1.on("error", (error) => {
264
+ reject(error);
265
+ });
266
+ });
267
+ await new Promise((resolve) => setTimeout(resolve, 100));
268
+ const inspection = await exec.inspect();
269
+ if (inspection.ExitCode !== 0) throw new Error(`Command failed with exit code ${inspection.ExitCode}: ${cmd.join(" ")}`);
270
+ }
271
+ };
272
+
273
+ //#endregion
274
+ //#region src/image-service.ts
275
+ const MAX_RETRIES = 5;
276
+ const INITIAL_DELAY_MS = 2e3;
277
+ const sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
278
+ const endOfStream = async (docker$1, stream$1) => {
279
+ return new Promise((resolve, reject) => {
280
+ docker$1.modem.followProgress(stream$1, (err) => err ? reject(err) : resolve(void 0));
281
+ });
282
+ };
283
+ function getOrgFromImage(imageName) {
284
+ const parts = imageName.split("/");
285
+ if (parts.length >= 3 && parts[0] === "ghcr.io") return parts[1];
286
+ return "unknown";
287
+ }
288
+ /** Fetch the configured updater image, if it isn't already available. */
289
+ const ImageService = {
290
+ async pull(imageName, sendMetric, force = false) {
291
+ if (!(imageName.startsWith("ghcr.io/") || imageName.startsWith("docker.pkg.github.com/"))) throw new Error("Only images distributed via docker.pkg.github.com or ghcr.io can be fetched");
292
+ const docker$1 = new Docker();
293
+ const org = getOrgFromImage(imageName);
294
+ try {
295
+ const image = await docker$1.getImage(imageName).inspect();
296
+ if (!force) {
297
+ logger.info(`Resolved ${imageName} to existing ${image.RepoDigests}`);
298
+ return;
299
+ }
300
+ } catch (e) {
301
+ if (e instanceof Error && !e.message.includes("no such image")) throw e;
302
+ }
303
+ await this.fetchImageWithRetry(imageName, {}, docker$1, sendMetric, org);
304
+ },
305
+ async fetchImageWithRetry(imageName, auth = {}, docker$1 = new Docker(), sendMetric, org) {
306
+ let attempt = 0;
307
+ while (attempt < MAX_RETRIES) try {
308
+ logger.info(`Pulling image ${imageName} (attempt ${attempt + 1})...`);
309
+ if (sendMetric) await sendMetric("ghcr_image_pull", "increment", 1, { org });
310
+ const stream$1 = await docker$1.pull(imageName, { authconfig: auth });
311
+ await endOfStream(docker$1, new Readable().wrap(stream$1));
312
+ logger.info(`Pulled image ${imageName}`);
313
+ return;
314
+ } catch (error) {
315
+ if (!(error instanceof Error)) throw error;
316
+ if (error.message.includes("429 Too Many Requests") || error.message.toLowerCase().includes("too many requests")) {
317
+ attempt++;
318
+ if (attempt >= MAX_RETRIES) {
319
+ logger.error(`Failed to pull image ${imageName} after ${MAX_RETRIES} attempts.`);
320
+ throw error;
321
+ }
322
+ const baseDelay = INITIAL_DELAY_MS * Math.pow(2, attempt);
323
+ const jitter = Math.random() * baseDelay;
324
+ const delay = baseDelay / 2 + jitter;
325
+ logger.warn(`Received Too Many Requests error. Retrying in ${(delay / 1e3).toFixed(2)} seconds...`);
326
+ await sleep(delay);
327
+ } else {
328
+ logger.error(`Fatal error pulling image ${imageName}: ${error.message}`);
329
+ throw error;
330
+ }
331
+ }
332
+ }
333
+ };
334
+
335
+ //#endregion
336
+ //#region package.json
337
+ var version = "0.1.0";
338
+
339
+ //#endregion
340
+ //#region src/params.ts
341
+ var JobParameters = class {
342
+ constructor(jobId, jobToken, credentialsToken, dependabotApiUrl, dependabotApiDockerUrl, updaterImage) {
343
+ this.jobId = jobId;
344
+ this.jobToken = jobToken;
345
+ this.credentialsToken = credentialsToken;
346
+ this.dependabotApiUrl = dependabotApiUrl;
347
+ this.dependabotApiDockerUrl = dependabotApiDockerUrl;
348
+ this.updaterImage = updaterImage;
349
+ }
350
+ };
351
+ function getJobParameters(input) {
352
+ return new JobParameters(parseInt(input.jobId, 10), input.jobToken, input.credentialsToken, input.dependabotApiUrl, input.dependabotApiDockerUrl, input.updaterImage);
353
+ }
354
+
355
+ //#endregion
356
+ //#region src/proxy.ts
357
+ const KEY_SIZE = 2048;
358
+ const KEY_EXPIRY_YEARS = 2;
359
+ const CONFIG_FILE_PATH = "/";
360
+ const CONFIG_FILE_NAME = "config.json";
361
+ const CA_CERT_INPUT_PATH$1 = "/usr/local/share/ca-certificates";
362
+ const CUSTOM_CA_CERT_NAME = "custom-ca-cert.crt";
363
+ const CERT_SUBJECT = [
364
+ {
365
+ name: "commonName",
366
+ value: "Dependabot Internal CA"
367
+ },
368
+ {
369
+ name: "organizationName",
370
+ value: "GitHub Inc."
371
+ },
372
+ {
373
+ shortName: "OU",
374
+ value: "Dependabot"
375
+ },
376
+ {
377
+ name: "countryName",
378
+ value: "US"
379
+ },
380
+ {
381
+ shortName: "ST",
382
+ value: "California"
383
+ },
384
+ {
385
+ name: "localityName",
386
+ value: "San Francisco"
387
+ }
388
+ ];
389
+ var ProxyBuilder = class {
390
+ constructor(docker$1, proxyImage, cachedMode) {
391
+ this.docker = docker$1;
392
+ this.proxyImage = proxyImage;
393
+ this.cachedMode = cachedMode;
394
+ }
395
+ async run(jobId, jobToken, dependabotApiUrl, credentials) {
396
+ const name = `dependabot-job-${jobId}-proxy`;
397
+ const config = await this.buildProxyConfig(credentials);
398
+ const cert = config.ca.cert;
399
+ const externalNetworkName = `dependabot-job-${jobId}-external-network`;
400
+ const externalNetwork = await this.ensureNetwork(externalNetworkName, false);
401
+ const internalNetworkName = `dependabot-job-${jobId}-internal-network`;
402
+ const internalNetwork = await this.ensureNetwork(internalNetworkName, true);
403
+ const container = await this.createContainer(jobId, jobToken, dependabotApiUrl, name, externalNetwork, internalNetwork, internalNetworkName);
404
+ await ContainerService.storeInput(CONFIG_FILE_NAME, CONFIG_FILE_PATH, container, config);
405
+ const customCAPath = this.customCAPath();
406
+ if (customCAPath) {
407
+ logger.info("Detected custom CA certificate, adding to proxy");
408
+ const customCert = (await readFile(customCAPath, "utf8")).toString();
409
+ await ContainerService.storeCert(CUSTOM_CA_CERT_NAME, CA_CERT_INPUT_PATH$1, container, customCert);
410
+ }
411
+ const stream$1 = await container.attach({
412
+ stream: true,
413
+ stdout: true,
414
+ stderr: true
415
+ });
416
+ container.modem.demuxStream(stream$1, outStream(" proxy"), errStream(" proxy"));
417
+ const url = async () => {
418
+ const containerInfo = await container.inspect();
419
+ if (containerInfo.State.Running === true) return `http://${containerInfo.NetworkSettings.Networks[`${internalNetworkName}`].IPAddress}:1080`;
420
+ else throw new Error("proxy container isn't running");
421
+ };
422
+ return {
423
+ container,
424
+ network: internalNetwork,
425
+ networkName: internalNetworkName,
426
+ url,
427
+ cert,
428
+ shutdown: async () => {
429
+ await container.stop();
430
+ await container.remove();
431
+ await Promise.all([externalNetwork.remove(), internalNetwork.remove()]);
432
+ }
433
+ };
434
+ }
435
+ async ensureNetwork(name, internal = true) {
436
+ const networks = await this.docker.listNetworks({ filters: JSON.stringify({ name: [name] }) });
437
+ if (networks.length > 0) return this.docker.getNetwork(networks[0].Id);
438
+ else return await this.docker.createNetwork({
439
+ Name: name,
440
+ Internal: internal
441
+ });
442
+ }
443
+ async buildProxyConfig(credentials) {
444
+ return {
445
+ all_credentials: credentials,
446
+ ca: await this.generateCertificateAuthority()
447
+ };
448
+ }
449
+ async generateCertificateAuthority() {
450
+ const { default: { md, pki } } = await import("node-forge");
451
+ const keys = pki.rsa.generateKeyPair(KEY_SIZE);
452
+ const cert = pki.createCertificate();
453
+ cert.publicKey = keys.publicKey;
454
+ cert.serialNumber = "01";
455
+ cert.validity.notBefore = /* @__PURE__ */ new Date();
456
+ cert.validity.notAfter = /* @__PURE__ */ new Date();
457
+ cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + KEY_EXPIRY_YEARS);
458
+ cert.setSubject(CERT_SUBJECT);
459
+ cert.setIssuer(CERT_SUBJECT);
460
+ cert.setExtensions([
461
+ {
462
+ name: "basicConstraints",
463
+ cA: true,
464
+ critical: true
465
+ },
466
+ {
467
+ name: "keyUsage",
468
+ digitalSignature: true,
469
+ keyEncipherment: true,
470
+ keyCertSign: true,
471
+ cRLSign: true,
472
+ critical: true
473
+ },
474
+ {
475
+ name: "extKeyUsage",
476
+ serverAuth: true,
477
+ clientAuth: true
478
+ },
479
+ { name: "subjectKeyIdentifier" },
480
+ {
481
+ name: "authorityKeyIdentifier",
482
+ keyIdentifier: true,
483
+ authorityCertIssuer: true,
484
+ authorityCertSerialNumber: cert.serialNumber
485
+ }
486
+ ]);
487
+ cert.sign(keys.privateKey, md.sha256.create());
488
+ return {
489
+ cert: pki.certificateToPem(cert),
490
+ key: pki.privateKeyToPem(keys.privateKey)
491
+ };
492
+ }
493
+ async createContainer(jobId, jobToken, dependabotApiUrl, containerName, externalNetwork, internalNetwork, internalNetworkName) {
494
+ const container = await this.docker.createContainer({
495
+ Image: this.proxyImage,
496
+ name: containerName,
497
+ AttachStdout: true,
498
+ AttachStderr: true,
499
+ Env: [
500
+ `http_proxy=${process.env.http_proxy || process.env.HTTP_PROXY || ""}`,
501
+ `https_proxy=${process.env.https_proxy || process.env.HTTPS_PROXY || ""}`,
502
+ `no_proxy=${process.env.no_proxy || process.env.NO_PROXY || ""}`,
503
+ `JOB_ID=${jobId}`,
504
+ `JOB_TOKEN=${jobToken}`,
505
+ `PROXY_CACHE=${this.cachedMode ? "true" : "false"}`,
506
+ `DEPENDABOT_API_URL=${dependabotApiUrl}`,
507
+ `ACTIONS_ID_TOKEN_REQUEST_TOKEN=${process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN || ""}`,
508
+ `ACTIONS_ID_TOKEN_REQUEST_URL=${process.env.ACTIONS_ID_TOKEN_REQUEST_URL || ""}`
509
+ ],
510
+ Entrypoint: [
511
+ "sh",
512
+ "-c",
513
+ "/usr/sbin/update-ca-certificates && /update-job-proxy"
514
+ ],
515
+ HostConfig: {
516
+ NetworkMode: internalNetworkName,
517
+ ExtraHosts: ["host.docker.internal:host-gateway"]
518
+ }
519
+ });
520
+ await externalNetwork.connect({ Container: container.id });
521
+ logger.info(`Created proxy container: ${container.id}`);
522
+ return container;
523
+ }
524
+ customCAPath() {
525
+ if ("CUSTOM_CA_PATH" in process.env) return process.env.CUSTOM_CA_PATH;
526
+ return process.env.NODE_EXTRA_CA_CERTS;
527
+ }
528
+ };
529
+
530
+ //#endregion
531
+ //#region src/updater-builder.ts
532
+ const JOB_OUTPUT_FILENAME = "output.json";
533
+ const JOB_OUTPUT_PATH = "/home/dependabot/dependabot-updater/output";
534
+ const JOB_INPUT_FILENAME = "job.json";
535
+ const JOB_INPUT_PATH = `/home/dependabot/dependabot-updater`;
536
+ const REPO_CONTENTS_PATH = "/home/dependabot/dependabot-updater/repo";
537
+ const CA_CERT_INPUT_PATH = "/usr/local/share/ca-certificates";
538
+ const CA_CERT_FILENAME = "dbot-ca.crt";
539
+ const UPDATER_MAX_MEMORY = 8 * 1024 * 1024 * 1024;
540
+ var UpdaterBuilder = class {
541
+ constructor(docker$1, jobParams, input, proxy$1, updaterImage) {
542
+ this.docker = docker$1;
543
+ this.jobParams = jobParams;
544
+ this.input = input;
545
+ this.proxy = proxy$1;
546
+ this.updaterImage = updaterImage;
547
+ }
548
+ async run(containerName) {
549
+ const proxyUrl = await this.proxy.url();
550
+ const container = await this.docker.createContainer({
551
+ Image: this.updaterImage,
552
+ name: containerName,
553
+ AttachStdout: true,
554
+ AttachStderr: true,
555
+ User: "dependabot",
556
+ Env: [
557
+ `GITHUB_ACTIONS=${process.env.GITHUB_ACTIONS}`,
558
+ `DEPENDABOT_JOB_ID=${this.jobParams.jobId}`,
559
+ `DEPENDABOT_JOB_TOKEN=`,
560
+ `DEPENDABOT_JOB_PATH=${JOB_INPUT_PATH}/${JOB_INPUT_FILENAME}`,
561
+ `DEPENDABOT_OPEN_TIMEOUT_IN_SECONDS=15`,
562
+ `DEPENDABOT_OUTPUT_PATH=${JOB_OUTPUT_PATH}/${JOB_OUTPUT_FILENAME}`,
563
+ `DEPENDABOT_REPO_CONTENTS_PATH=${REPO_CONTENTS_PATH}`,
564
+ `DEPENDABOT_API_URL=${this.jobParams.dependabotApiDockerUrl}`,
565
+ `SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt`,
566
+ `http_proxy=${proxyUrl}`,
567
+ `HTTP_PROXY=${proxyUrl}`,
568
+ `https_proxy=${proxyUrl}`,
569
+ `HTTPS_PROXY=${proxyUrl}`,
570
+ `UPDATER_ONE_CONTAINER=1`,
571
+ `ENABLE_CONNECTIVITY_CHECK=${process.env.DEPENDABOT_ENABLE_CONNECTIVITY_CHECK || "1"}`,
572
+ ...process.platform === "darwin" ? [`DOTNET_EnableWriteXorExecute=0`] : []
573
+ ],
574
+ Cmd: ["/bin/sh"],
575
+ Tty: true,
576
+ HostConfig: {
577
+ Memory: UPDATER_MAX_MEMORY,
578
+ NetworkMode: this.proxy.networkName
579
+ }
580
+ });
581
+ await ContainerService.storeCert(CA_CERT_FILENAME, CA_CERT_INPUT_PATH, container, this.proxy.cert);
582
+ await ContainerService.storeInput(JOB_INPUT_FILENAME, JOB_INPUT_PATH, container, this.input);
583
+ logger.info(`Created container: ${container.id}`);
584
+ return container;
585
+ }
586
+ };
587
+
588
+ //#endregion
589
+ //#region src/updater.ts
590
+ var Updater = class {
591
+ docker;
592
+ constructor(updaterImage, proxyImage, params, job, credentials) {
593
+ this.updaterImage = updaterImage;
594
+ this.proxyImage = proxyImage;
595
+ this.params = params;
596
+ this.job = job;
597
+ this.credentials = credentials;
598
+ this.docker = new Docker();
599
+ this.job["credentials-metadata"] = this.generateCredentialsMetadata();
600
+ }
601
+ /**
602
+ * Execute an update job and report the result to Dependabot API.
603
+ */
604
+ async runUpdater() {
605
+ const cachedMode = Object.hasOwn(this.job.experiments, "proxy-cached") === true;
606
+ const proxy$1 = await new ProxyBuilder(this.docker, this.proxyImage, cachedMode).run(this.params.jobId, this.params.jobToken, this.params.dependabotApiUrl, this.credentials);
607
+ await proxy$1.container.start();
608
+ try {
609
+ await this.runUpdate(proxy$1);
610
+ return true;
611
+ } finally {
612
+ await this.cleanup(proxy$1);
613
+ }
614
+ }
615
+ generateCredentialsMetadata() {
616
+ const unique = /* @__PURE__ */ new Set();
617
+ const result = [];
618
+ for (const credential of this.credentials) {
619
+ if (credential.type === "jit_access") continue;
620
+ const obj = { type: credential.type };
621
+ if (credential.host !== void 0) obj.host = credential.host;
622
+ if (credential.registry !== void 0) obj.registry = credential.registry;
623
+ if (credential.url !== void 0) obj.url = credential.url;
624
+ this.setRegistryFromUrl(obj, credential);
625
+ if (credential["index-url"] !== void 0) obj["index-url"] = credential["index-url"];
626
+ this.setIndexUrlFromUrl(obj, credential);
627
+ if (credential["env-key"] !== void 0) obj["env-key"] = credential["env-key"];
628
+ if (credential.organization !== void 0) obj.organization = credential.organization;
629
+ if (credential["replaces-base"] !== void 0) obj["replaces-base"] = credential["replaces-base"];
630
+ if (credential["public-key-fingerprint"] !== void 0) obj["public-key-fingerprint"] = credential["public-key-fingerprint"];
631
+ if (credential.repo !== void 0) obj.repo = credential.repo;
632
+ const key = JSON.stringify(obj);
633
+ if (!unique.has(key)) {
634
+ unique.add(key);
635
+ result.push(obj);
636
+ }
637
+ }
638
+ return result;
639
+ }
640
+ setRegistryFromUrl(obj, credential) {
641
+ if (![
642
+ "npm_registry",
643
+ "composer_repository",
644
+ "docker_registry"
645
+ ].includes(credential.type)) return;
646
+ if (!credential.registry && credential.url) try {
647
+ const parsedURL = new URL(credential.url);
648
+ obj.registry = parsedURL.hostname;
649
+ if (credential.type === "npm_registry") obj.registry += parsedURL.pathname;
650
+ } catch {}
651
+ }
652
+ setIndexUrlFromUrl(obj, credential) {
653
+ if (credential.type !== "python_index") return;
654
+ if (credential["index-url"]) return;
655
+ if (credential.url) try {
656
+ obj["index-url"] = credential.url;
657
+ } catch {}
658
+ }
659
+ async runUpdate(proxy$1) {
660
+ const name = `dependabot-job-${this.params.jobId}`;
661
+ const container = await this.createContainer(proxy$1, name, { job: this.job });
662
+ await ContainerService.run(container, this.job.command);
663
+ }
664
+ async createContainer(proxy$1, containerName, input) {
665
+ return new UpdaterBuilder(this.docker, this.params, input, proxy$1, this.updaterImage).run(containerName);
666
+ }
667
+ async cleanup(proxy$1) {
668
+ await proxy$1.shutdown();
669
+ }
670
+ };
671
+
672
+ //#endregion
673
+ //#region src/job-runner.ts
674
+ var JobRunnerImagingError = class extends Error {};
675
+ var JobRunnerUpdaterError = class extends Error {};
676
+ var JobRunner = class {
677
+ options;
678
+ constructor(options) {
679
+ this.options = options;
680
+ }
681
+ async run() {
682
+ const { dependabotApiUrl, dependabotApiDockerUrl, jobId, jobToken, credentialsToken, secretMasker } = this.options;
683
+ const params = getJobParameters({
684
+ jobId,
685
+ jobToken,
686
+ credentialsToken,
687
+ dependabotApiUrl,
688
+ dependabotApiDockerUrl: dependabotApiDockerUrl ?? dependabotApiUrl,
689
+ updaterImage: this.options.updaterImage
690
+ });
691
+ const apiClient = new ApiClient(new InnerApiClient({ baseUrl: dependabotApiUrl.replace("host.docker.internal", "localhost") }), params, jobToken, credentialsToken, secretMasker);
692
+ const job = await apiClient.getJobDetails();
693
+ const updaterImage = params.updaterImage || updaterImageName(job["package-manager"]);
694
+ const sendMetricsWithPackageManager = async (name, metricType, value, additionalTags = {}) => {
695
+ try {
696
+ await apiClient.sendMetrics(name, metricType, value, {
697
+ package_manager: job["package-manager"],
698
+ ...additionalTags
699
+ });
700
+ } catch (error) {
701
+ logger.warn(`Metric sending failed for ${name}: ${error.message}`);
702
+ }
703
+ };
704
+ const updater = new Updater(updaterImage, PROXY_IMAGE_NAME, params, job, await apiClient.getCredentials() || []);
705
+ try {
706
+ await ImageService.pull(updaterImage, sendMetricsWithPackageManager);
707
+ await ImageService.pull(PROXY_IMAGE_NAME, sendMetricsWithPackageManager);
708
+ } catch (err) {
709
+ if (err instanceof Error) throw new JobRunnerImagingError(err.message);
710
+ }
711
+ try {
712
+ await updater.runUpdater();
713
+ } catch (err) {
714
+ if (err instanceof Error) throw new JobRunnerUpdaterError(err.message);
715
+ }
716
+ }
717
+ };
718
+ async function runJob({ jobId, usage,...options }) {
719
+ const started = /* @__PURE__ */ new Date();
720
+ let success = false;
721
+ let message;
722
+ try {
723
+ await new JobRunner({
724
+ jobId,
725
+ ...options
726
+ }).run();
727
+ success = true;
728
+ } catch (err) {
729
+ if (err instanceof JobRunnerImagingError) message = `Error fetching updater images: ${err.message}`;
730
+ else if (err instanceof JobRunnerUpdaterError) message = `Error running updater: ${err.message}`;
731
+ else if (err instanceof CredentialFetchingError) message = `Dependabot was unable to retrieve job credentials: ${err.message}`;
732
+ else message = `Unknown error: ${err.message}`;
733
+ }
734
+ const duration = Date.now() - started.getTime();
735
+ const data = {
736
+ ...usage,
737
+ host: {
738
+ platform: os.platform(),
739
+ release: os.release(),
740
+ arch: os.arch(),
741
+ "machine-hash": crypto.createHash("sha256").update(os.hostname()).digest("hex")
742
+ },
743
+ version,
744
+ id: jobId,
745
+ started,
746
+ duration,
747
+ success
748
+ };
749
+ try {
750
+ const json = JSON.stringify(data);
751
+ logger.debug(`Usage telemetry data: ${json}`);
752
+ const resp = await fetch("https://dashboard.paklo.app/api/usage-telemetry", {
753
+ method: "POST",
754
+ headers: { "Content-Type": "application/json" },
755
+ body: json
756
+ });
757
+ if (!resp.ok) logger.debug(`Failed to send usage telemetry data: ${resp.status} ${resp.statusText}`);
758
+ } catch (err) {
759
+ logger.debug(`Failed to send usage telemetry data: ${err.message}`);
760
+ }
761
+ logger.info(`Update job ${jobId} completed`);
762
+ return {
763
+ success,
764
+ message
765
+ };
766
+ }
767
+
768
+ //#endregion
769
+ export { JobDetailsFetchingError as S, repositoryName as _, Updater as a, ApiClient as b, JobParameters as c, getOrgFromImage as d, ContainerRuntimeError as f, hasDigest as g, digestName as h, runJob as i, getJobParameters as l, PROXY_IMAGE_NAME as m, JobRunnerImagingError as n, UpdaterBuilder as o, ContainerService as p, JobRunnerUpdaterError as r, ProxyBuilder as s, JobRunner as t, ImageService as u, updaterImageName as v, CredentialFetchingError as x, updaterImages as y };
770
+ //# sourceMappingURL=job-runner-BUl_fCJS.js.map