@determinate-systems/detsys-ts 1.0.0 → 2.0.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.
- package/dist/index.d.mts +263 -31
- package/dist/index.mjs +1069 -451
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -1
package/dist/index.mjs
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import * as
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
2
|
+
import * as nodeFs from "node:fs";
|
|
3
|
+
import fs, { createReadStream } from "node:fs";
|
|
4
|
+
import os, { tmpdir } from "node:os";
|
|
6
5
|
import { promisify } from "node:util";
|
|
7
6
|
import * as actionsCore from "@actions/core";
|
|
8
7
|
import * as exec$1 from "@actions/exec";
|
|
9
|
-
import os from "os";
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
8
|
+
import os$1 from "os";
|
|
9
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
10
|
+
import * as otelApi from "@opentelemetry/api";
|
|
11
|
+
import { SeverityNumber, logs } from "@opentelemetry/api-logs";
|
|
12
|
+
import { AsyncLocalStorageContextManager } from "@opentelemetry/context-async-hooks";
|
|
13
|
+
import * as otelCore from "@opentelemetry/core";
|
|
14
|
+
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
|
|
15
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
|
|
16
|
+
import * as otelResources from "@opentelemetry/resources";
|
|
17
|
+
import * as sdkLogs from "@opentelemetry/sdk-logs";
|
|
18
|
+
import * as sdkTrace from "@opentelemetry/sdk-trace-base";
|
|
19
|
+
import * as semconv from "@opentelemetry/semantic-conventions";
|
|
13
20
|
import got, { TimeoutError } from "got";
|
|
14
21
|
import { resolveSrv } from "node:dns/promises";
|
|
15
22
|
import * as actionsCache from "@actions/cache";
|
|
23
|
+
import * as semconvIncubating from "@opentelemetry/semantic-conventions/incubating";
|
|
16
24
|
import { exec } from "node:child_process";
|
|
17
|
-
import
|
|
25
|
+
import fs$1, { chmod, copyFile, mkdir, readFile } from "node:fs/promises";
|
|
26
|
+
import path from "node:path";
|
|
18
27
|
//#region src/linux-release-info.ts
|
|
19
28
|
/*!
|
|
20
29
|
* linux-release-info
|
|
@@ -26,7 +35,7 @@ import * as path from "node:path";
|
|
|
26
35
|
* Licensed under MIT
|
|
27
36
|
* Copyright (c) 2018-2020 [Samuel Carreira]
|
|
28
37
|
*/
|
|
29
|
-
const readFileAsync = promisify(fs
|
|
38
|
+
const readFileAsync = promisify(fs.readFile);
|
|
30
39
|
const linuxReleaseInfoOptionsDefaults = {
|
|
31
40
|
mode: "async",
|
|
32
41
|
customFile: null,
|
|
@@ -44,7 +53,7 @@ function releaseInfo(infoOptions) {
|
|
|
44
53
|
...infoOptions
|
|
45
54
|
};
|
|
46
55
|
const searchOsReleaseFileList = osReleaseFileList(options.customFile);
|
|
47
|
-
if (os
|
|
56
|
+
if (os.type() !== "Linux") {
|
|
48
57
|
if (options.mode === "sync") return getOsInfo();
|
|
49
58
|
else return Promise.resolve(getOsInfo());
|
|
50
59
|
}
|
|
@@ -93,11 +102,11 @@ function osReleaseFileList(customFile) {
|
|
|
93
102
|
*/
|
|
94
103
|
function getOsInfo() {
|
|
95
104
|
return {
|
|
96
|
-
type: os
|
|
97
|
-
platform: os
|
|
98
|
-
hostname: os
|
|
99
|
-
arch: os
|
|
100
|
-
release: os
|
|
105
|
+
type: os.type(),
|
|
106
|
+
platform: os.platform(),
|
|
107
|
+
hostname: os.hostname(),
|
|
108
|
+
arch: os.arch(),
|
|
109
|
+
release: os.release()
|
|
101
110
|
};
|
|
102
111
|
}
|
|
103
112
|
async function readAsyncOsReleaseFile(fileList, options) {
|
|
@@ -117,7 +126,7 @@ function readSyncOsreleaseFile(releaseFileList, options) {
|
|
|
117
126
|
let fileData = null;
|
|
118
127
|
for (const osReleaseFile of releaseFileList) try {
|
|
119
128
|
if (options.debug) console.log(`Trying to read '${osReleaseFile}'...`);
|
|
120
|
-
fileData = fs
|
|
129
|
+
fileData = fs.readFileSync(osReleaseFile, "binary");
|
|
121
130
|
if (options.debug) console.log(`Read data:\n${fileData}`);
|
|
122
131
|
break;
|
|
123
132
|
} catch (error) {
|
|
@@ -191,11 +200,11 @@ function getPropertyWithDefault(data, name, defaultValue) {
|
|
|
191
200
|
/**
|
|
192
201
|
* The Action runner's platform.
|
|
193
202
|
*/
|
|
194
|
-
const platform = os.platform();
|
|
203
|
+
const platform = os$1.platform();
|
|
195
204
|
/**
|
|
196
205
|
* The Action runner's architecture.
|
|
197
206
|
*/
|
|
198
|
-
const arch = os.arch();
|
|
207
|
+
const arch = os$1.arch();
|
|
199
208
|
/**
|
|
200
209
|
* Whether the Action runner is a Windows system.
|
|
201
210
|
*/
|
|
@@ -222,112 +231,6 @@ async function getDetails() {
|
|
|
222
231
|
};
|
|
223
232
|
}
|
|
224
233
|
//#endregion
|
|
225
|
-
//#region src/errors.ts
|
|
226
|
-
/**
|
|
227
|
-
* Coerce a value of type `unknown` into a string.
|
|
228
|
-
*/
|
|
229
|
-
function stringifyError(e) {
|
|
230
|
-
if (e instanceof Error) return e.message;
|
|
231
|
-
else if (typeof e === "string") return e;
|
|
232
|
-
else return JSON.stringify(e);
|
|
233
|
-
}
|
|
234
|
-
//#endregion
|
|
235
|
-
//#region src/backtrace.ts
|
|
236
|
-
/**
|
|
237
|
-
* @packageDocumentation
|
|
238
|
-
* Collects backtraces for executables for diagnostics
|
|
239
|
-
*/
|
|
240
|
-
const START_SLOP_SECONDS = 5;
|
|
241
|
-
async function collectBacktraces(prefixes, programNameDenyList, startTimestampMs) {
|
|
242
|
-
if (isMacOS) return await collectBacktracesMacOS(prefixes, programNameDenyList, startTimestampMs);
|
|
243
|
-
if (isLinux) return await collectBacktracesSystemd(prefixes, programNameDenyList, startTimestampMs);
|
|
244
|
-
return /* @__PURE__ */ new Map();
|
|
245
|
-
}
|
|
246
|
-
async function collectBacktracesMacOS(prefixes, programNameDenyList, startTimestampMs) {
|
|
247
|
-
const backtraces = /* @__PURE__ */ new Map();
|
|
248
|
-
try {
|
|
249
|
-
const { stdout: logJson } = await exec$1.getExecOutput("log", [
|
|
250
|
-
"show",
|
|
251
|
-
"--style",
|
|
252
|
-
"json",
|
|
253
|
-
"--last",
|
|
254
|
-
"1m",
|
|
255
|
-
"--no-info",
|
|
256
|
-
"--predicate",
|
|
257
|
-
"sender = 'ReportCrash'"
|
|
258
|
-
], { silent: true });
|
|
259
|
-
const sussyArray = JSON.parse(logJson);
|
|
260
|
-
if (!Array.isArray(sussyArray)) throw new Error(`Log json isn't an array: ${logJson}`);
|
|
261
|
-
if (sussyArray.length > 0) {
|
|
262
|
-
actionsCore.info(`Collecting crash data...`);
|
|
263
|
-
const delay = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
264
|
-
await delay(5e3);
|
|
265
|
-
}
|
|
266
|
-
} catch {
|
|
267
|
-
actionsCore.debug("Failed to check logs for in-progress crash dumps; now proceeding with the assumption that all crash dumps completed.");
|
|
268
|
-
}
|
|
269
|
-
const dirs = [["system", "/Library/Logs/DiagnosticReports/"], ["user", `${process.env["HOME"]}/Library/Logs/DiagnosticReports/`]];
|
|
270
|
-
for (const [source, dir] of dirs) {
|
|
271
|
-
const fileNames = (await readdir(dir)).filter((fileName) => {
|
|
272
|
-
return prefixes.some((prefix) => fileName.startsWith(prefix));
|
|
273
|
-
}).filter((fileName) => {
|
|
274
|
-
return !programNameDenyList.some((programName) => fileName.startsWith(programName));
|
|
275
|
-
}).filter((fileName) => {
|
|
276
|
-
return !fileName.endsWith(".diag");
|
|
277
|
-
});
|
|
278
|
-
const doGzip = promisify(gzip);
|
|
279
|
-
for (const fileName of fileNames) try {
|
|
280
|
-
if ((await stat(`${dir}/${fileName}`)).ctimeMs >= startTimestampMs) {
|
|
281
|
-
const buf = await doGzip(await readFile(`${dir}/${fileName}`));
|
|
282
|
-
backtraces.set(`backtrace_value_${source}_${fileName}`, buf.toString("base64"));
|
|
283
|
-
}
|
|
284
|
-
} catch (innerError) {
|
|
285
|
-
backtraces.set(`backtrace_failure_${source}_${fileName}`, stringifyError(innerError));
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
return backtraces;
|
|
289
|
-
}
|
|
290
|
-
async function collectBacktracesSystemd(prefixes, programNameDenyList, startTimestampMs) {
|
|
291
|
-
const sinceSeconds = Math.ceil((Date.now() - startTimestampMs) / 1e3) + START_SLOP_SECONDS;
|
|
292
|
-
const backtraces = /* @__PURE__ */ new Map();
|
|
293
|
-
const coredumps = [];
|
|
294
|
-
try {
|
|
295
|
-
const { stdout: coredumpjson } = await exec$1.getExecOutput("coredumpctl", [
|
|
296
|
-
"--json=pretty",
|
|
297
|
-
"list",
|
|
298
|
-
"--since",
|
|
299
|
-
`${sinceSeconds} seconds ago`
|
|
300
|
-
], { silent: true });
|
|
301
|
-
const sussyArray = JSON.parse(coredumpjson);
|
|
302
|
-
if (!Array.isArray(sussyArray)) throw new Error(`Coredump isn't an array: ${coredumpjson}`);
|
|
303
|
-
for (const sussyObject of sussyArray) {
|
|
304
|
-
const keys = Object.keys(sussyObject);
|
|
305
|
-
if (keys.includes("exe") && keys.includes("pid")) {
|
|
306
|
-
if (typeof sussyObject.exe == "string" && typeof sussyObject.pid == "number") {
|
|
307
|
-
const execParts = sussyObject.exe.split("/");
|
|
308
|
-
const binaryName = execParts[execParts.length - 1];
|
|
309
|
-
if (prefixes.some((prefix) => binaryName.startsWith(prefix)) && !programNameDenyList.includes(binaryName)) coredumps.push({
|
|
310
|
-
exe: sussyObject.exe,
|
|
311
|
-
pid: sussyObject.pid
|
|
312
|
-
});
|
|
313
|
-
} else actionsCore.debug(`Mysterious coredump entry missing exe string and/or pid number: ${JSON.stringify(sussyObject)}`);
|
|
314
|
-
} else actionsCore.debug(`Mysterious coredump entry missing exe value and/or pid value: ${JSON.stringify(sussyObject)}`);
|
|
315
|
-
}
|
|
316
|
-
} catch (innerError) {
|
|
317
|
-
actionsCore.debug(`Cannot collect backtraces: ${stringifyError(innerError)}`);
|
|
318
|
-
return backtraces;
|
|
319
|
-
}
|
|
320
|
-
const doGzip = promisify(gzip);
|
|
321
|
-
for (const coredump of coredumps) try {
|
|
322
|
-
const { stdout: logText } = await exec$1.getExecOutput("coredumpctl", ["info", `${coredump.pid}`], { silent: true });
|
|
323
|
-
const buf = await doGzip(logText);
|
|
324
|
-
backtraces.set(`backtrace_value_${coredump.pid}`, buf.toString("base64"));
|
|
325
|
-
} catch (innerError) {
|
|
326
|
-
backtraces.set(`backtrace_failure_${coredump.pid}`, stringifyError(innerError));
|
|
327
|
-
}
|
|
328
|
-
return backtraces;
|
|
329
|
-
}
|
|
330
|
-
//#endregion
|
|
331
234
|
//#region src/checksums.ts
|
|
332
235
|
/**
|
|
333
236
|
* @packageDocumentation
|
|
@@ -463,6 +366,418 @@ function hashEnvironmentVariables(prefix, variables) {
|
|
|
463
366
|
return `${prefix}-${hash.digest("hex")}`;
|
|
464
367
|
}
|
|
465
368
|
//#endregion
|
|
369
|
+
//#region src/errors.ts
|
|
370
|
+
/**
|
|
371
|
+
* Coerce a value of type `unknown` into a string.
|
|
372
|
+
*/
|
|
373
|
+
function stringifyError(e) {
|
|
374
|
+
if (e instanceof Error) return e.message;
|
|
375
|
+
else if (typeof e === "string") return e;
|
|
376
|
+
else return JSON.stringify(e);
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/telemetry.ts
|
|
380
|
+
/**
|
|
381
|
+
* @packageDocumentation
|
|
382
|
+
* OpenTelemetry traces and logs for Determinate Systems' GitHub Actions.
|
|
383
|
+
*
|
|
384
|
+
* The OpenTelemetry API is a no-op until a provider is registered globally.
|
|
385
|
+
* That means instrumentation call sites -- spans, log records -- can be
|
|
386
|
+
* written unconditionally: when export is disabled they cost nothing and no
|
|
387
|
+
* branching is needed at the call site.
|
|
388
|
+
*
|
|
389
|
+
* The SDK configures itself from the standard `OTEL_*` environment variables.
|
|
390
|
+
* This module only supplies defaults for the variables the user has not set,
|
|
391
|
+
* so every documented OpenTelemetry knob works here as it does anywhere else.
|
|
392
|
+
*/
|
|
393
|
+
/** The instrumentation scope name for everything this library emits. */
|
|
394
|
+
const SCOPE_NAME = "detsys-ts";
|
|
395
|
+
/**
|
|
396
|
+
* The OTLP/HTTP collector for all Actions.
|
|
397
|
+
* The exporters add `/v1/traces` and `/v1/logs` to this URL.
|
|
398
|
+
*
|
|
399
|
+
* This collector is a fixed service.
|
|
400
|
+
* It is not one of the install.determinate.systems backends.
|
|
401
|
+
* Thus it does not use their SRV failover.
|
|
402
|
+
*/
|
|
403
|
+
const DEFAULT_OTLP_ENDPOINT = "https://otel.determinate.systems";
|
|
404
|
+
/**
|
|
405
|
+
* The token for {@link DEFAULT_OTLP_ENDPOINT}.
|
|
406
|
+
* The exporters send it as `Authorization: Bearer <token>`.
|
|
407
|
+
* That is the default scheme of the collector's `bearertokenauth` extension.
|
|
408
|
+
*
|
|
409
|
+
* This token is public.
|
|
410
|
+
* It ships in `dist/`, on npm, and in each workflow that uses this library.
|
|
411
|
+
* It permits telemetry writes and no other operation.
|
|
412
|
+
* Change it in the collector configuration and in this file at the same time.
|
|
413
|
+
*/
|
|
414
|
+
const OTLP_INGEST_TOKEN = "8bfa2d8b689352981286f0149c4e55cc0dff30a4f7a735b560e31479904a74e1";
|
|
415
|
+
/**
|
|
416
|
+
* How long to wait for buffered spans and logs to reach the collector before
|
|
417
|
+
* giving up. The Action's process exits immediately afterward, so this is a
|
|
418
|
+
* hard ceiling on how much a slow collector can delay a workflow.
|
|
419
|
+
*/
|
|
420
|
+
const SHUTDOWN_TIMEOUT_MS = 5e3;
|
|
421
|
+
/**
|
|
422
|
+
* The default for `OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT`.
|
|
423
|
+
*
|
|
424
|
+
* The SDK's own default is unlimited. Attributes here can carry pasted
|
|
425
|
+
* command output and other unbounded text, which the collector should not
|
|
426
|
+
* have to absorb, so cap them. File-sized payloads go out as log records
|
|
427
|
+
* instead: a log record's body is not an attribute and is not truncated.
|
|
428
|
+
*/
|
|
429
|
+
const DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT = 8192;
|
|
430
|
+
/** The OTLP environment variables a child process inherits from this run. */
|
|
431
|
+
const OTLP_EXPORT_VARIABLES = [
|
|
432
|
+
"OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
433
|
+
"OTEL_EXPORTER_OTLP_HEADERS",
|
|
434
|
+
"OTEL_EXPORTER_OTLP_COMPRESSION"
|
|
435
|
+
];
|
|
436
|
+
/**
|
|
437
|
+
* Our own propagator instance, rather than the global one.
|
|
438
|
+
*
|
|
439
|
+
* The global propagator only exists once {@link Telemetry.start} has
|
|
440
|
+
* registered it, which would make traceparent handling silently depend on
|
|
441
|
+
* start-up ordering. Owning an instance keeps {@link traceparentOf} and {@link
|
|
442
|
+
* contextFromTraceparent} correct no matter when they're called.
|
|
443
|
+
*/
|
|
444
|
+
const PROPAGATOR = new otelCore.W3CTraceContextPropagator();
|
|
445
|
+
const SEVERITY = {
|
|
446
|
+
debug: SeverityNumber.DEBUG,
|
|
447
|
+
info: SeverityNumber.INFO,
|
|
448
|
+
notice: SeverityNumber.INFO2,
|
|
449
|
+
warning: SeverityNumber.WARN,
|
|
450
|
+
error: SeverityNumber.ERROR
|
|
451
|
+
};
|
|
452
|
+
/**
|
|
453
|
+
* Whether this run exports telemetry at all.
|
|
454
|
+
*
|
|
455
|
+
* `OTEL_SDK_DISABLED=true` is the standard way to turn the export off. An
|
|
456
|
+
* empty `OTEL_EXPORTER_OTLP_ENDPOINT` does the same, which is what this
|
|
457
|
+
* library documented before `OTEL_SDK_DISABLED` was in the specification.
|
|
458
|
+
*/
|
|
459
|
+
function exportEnabled() {
|
|
460
|
+
if (otelCore.getBooleanFromEnv("OTEL_SDK_DISABLED")) return false;
|
|
461
|
+
const endpoint = process.env["OTEL_EXPORTER_OTLP_ENDPOINT"];
|
|
462
|
+
if (endpoint !== void 0 && endpoint.trim() === "") return false;
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Fill in the `OTEL_*` variables this run needs and the user has not set.
|
|
467
|
+
*
|
|
468
|
+
* From here on the exporters read their whole configuration from the
|
|
469
|
+
* environment, exactly as they would in any other OpenTelemetry program.
|
|
470
|
+
* Child processes inherit the same variables, so their telemetry reaches the
|
|
471
|
+
* same collector without any further arrangement.
|
|
472
|
+
*/
|
|
473
|
+
function applyOtlpEnvironmentDefaults() {
|
|
474
|
+
if (otelCore.getStringFromEnv("OTEL_EXPORTER_OTLP_ENDPOINT") === void 0) process.env["OTEL_EXPORTER_OTLP_ENDPOINT"] = DEFAULT_OTLP_ENDPOINT;
|
|
475
|
+
if (exportsToDefaultCollector()) {
|
|
476
|
+
const headers = otelCore.parseKeyPairsIntoRecord(otelCore.getStringFromEnv("OTEL_EXPORTER_OTLP_HEADERS"));
|
|
477
|
+
if (!Object.keys(headers).some((name) => name.toLowerCase() === "authorization")) {
|
|
478
|
+
headers["Authorization"] = `Bearer ${OTLP_INGEST_TOKEN}`;
|
|
479
|
+
process.env["OTEL_EXPORTER_OTLP_HEADERS"] = encodeOtlpHeaders(headers);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (otelCore.getStringFromEnv("OTEL_EXPORTER_OTLP_COMPRESSION") === void 0) process.env["OTEL_EXPORTER_OTLP_COMPRESSION"] = "gzip";
|
|
483
|
+
if (otelCore.getNumberFromEnv("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT") === void 0) process.env["OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT"] = `${DEFAULT_ATTRIBUTE_VALUE_LENGTH_LIMIT}`;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Whether this run sends its data to {@link DEFAULT_OTLP_ENDPOINT}.
|
|
487
|
+
*
|
|
488
|
+
* Only that collector gets {@link OTLP_INGEST_TOKEN}. A collector the user
|
|
489
|
+
* chose must not receive our credentials.
|
|
490
|
+
*/
|
|
491
|
+
function exportsToDefaultCollector() {
|
|
492
|
+
const endpoint = otelCore.getStringFromEnv("OTEL_EXPORTER_OTLP_ENDPOINT");
|
|
493
|
+
if (endpoint === void 0) return false;
|
|
494
|
+
try {
|
|
495
|
+
return new URL(endpoint).toString() === new URL(DEFAULT_OTLP_ENDPOINT).toString();
|
|
496
|
+
} catch {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* The OTLP variables in the environment, for a child process that does not
|
|
502
|
+
* inherit ours.
|
|
503
|
+
*/
|
|
504
|
+
function otlpExportEnvironment() {
|
|
505
|
+
const environment = {};
|
|
506
|
+
for (const name of OTLP_EXPORT_VARIABLES) {
|
|
507
|
+
const value = otelCore.getStringFromEnv(name);
|
|
508
|
+
if (value !== void 0) environment[name] = value;
|
|
509
|
+
}
|
|
510
|
+
return environment;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Make the value of `OTEL_EXPORTER_OTLP_HEADERS`.
|
|
514
|
+
*
|
|
515
|
+
* The variable uses the W3C baggage format.
|
|
516
|
+
* The reader decodes each percent-encoded value.
|
|
517
|
+
* Thus you must encode the space in `Bearer <token>`.
|
|
518
|
+
* If you do not encode it, the scheme and the token become two entries.
|
|
519
|
+
*/
|
|
520
|
+
function encodeOtlpHeaders(headers) {
|
|
521
|
+
return Object.entries(headers).map(([name, value]) => `${encodeURIComponent(name)}=${encodeURIComponent(value)}`).join(",");
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* The generator of the trace and span IDs of this run.
|
|
525
|
+
*
|
|
526
|
+
* It makes random IDs, as the default generator does.
|
|
527
|
+
* It can also give one span an identity that you supply.
|
|
528
|
+
* That is how a span that one process announces starts in a different process.
|
|
529
|
+
* See {@link Telemetry.startAnnouncedSpan}.
|
|
530
|
+
*/
|
|
531
|
+
var PinnedIdGenerator = class {
|
|
532
|
+
/** Give the next span this identity. */
|
|
533
|
+
pin(traceId, spanId) {
|
|
534
|
+
this.traceId = traceId;
|
|
535
|
+
this.spanId = spanId;
|
|
536
|
+
}
|
|
537
|
+
/** Give each subsequent span a random identity again. */
|
|
538
|
+
unpin() {
|
|
539
|
+
this.traceId = void 0;
|
|
540
|
+
this.spanId = void 0;
|
|
541
|
+
}
|
|
542
|
+
generateTraceId() {
|
|
543
|
+
return this.traceId ?? randomHex(16);
|
|
544
|
+
}
|
|
545
|
+
generateSpanId() {
|
|
546
|
+
return this.spanId ?? randomHex(8);
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
/**
|
|
550
|
+
* Owns the OpenTelemetry SDK's lifecycle. Constructing this does nothing on
|
|
551
|
+
* its own; `start()` registers the global providers and `shutdown()` flushes
|
|
552
|
+
* whatever is buffered.
|
|
553
|
+
*/
|
|
554
|
+
var Telemetry = class {
|
|
555
|
+
/** Whether OTLP export is actually running. */
|
|
556
|
+
get enabled() {
|
|
557
|
+
return this.tracerProvider !== void 0;
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Register the global tracer and logger providers.
|
|
561
|
+
*
|
|
562
|
+
* Safe to call at most once. If it throws, telemetry stays disabled and the
|
|
563
|
+
* Action carries on: instrumentation degrades to the API's no-ops rather
|
|
564
|
+
* than failing the workflow.
|
|
565
|
+
*/
|
|
566
|
+
start(options) {
|
|
567
|
+
if (this.enabled || !exportEnabled()) return;
|
|
568
|
+
try {
|
|
569
|
+
applyOtlpEnvironmentDefaults();
|
|
570
|
+
const resource = otelResources.defaultResource().merge(otelResources.resourceFromAttributes({
|
|
571
|
+
[semconv.ATTR_SERVICE_NAME]: options.serviceName,
|
|
572
|
+
...options.serviceVersion === void 0 ? {} : { [semconv.ATTR_SERVICE_VERSION]: options.serviceVersion },
|
|
573
|
+
...options.resourceAttributes
|
|
574
|
+
})).merge(otelResources.detectResources({ detectors: [otelResources.envDetector] }));
|
|
575
|
+
this.idGenerator = new PinnedIdGenerator();
|
|
576
|
+
this.tracerProvider = new sdkTrace.BasicTracerProvider({
|
|
577
|
+
resource,
|
|
578
|
+
idGenerator: this.idGenerator,
|
|
579
|
+
spanProcessors: [new sdkTrace.BatchSpanProcessor(new OTLPTraceExporter())]
|
|
580
|
+
});
|
|
581
|
+
this.loggerProvider = new sdkLogs.LoggerProvider({
|
|
582
|
+
resource,
|
|
583
|
+
logRecordLimits: { attributeValueLengthLimit: otelCore.getNumberFromEnv("OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT") },
|
|
584
|
+
processors: [new sdkLogs.BatchLogRecordProcessor({ exporter: new OTLPLogExporter() })]
|
|
585
|
+
});
|
|
586
|
+
otelApi.context.setGlobalContextManager(new AsyncLocalStorageContextManager().enable());
|
|
587
|
+
otelApi.propagation.setGlobalPropagator(PROPAGATOR);
|
|
588
|
+
otelApi.trace.setGlobalTracerProvider(this.tracerProvider);
|
|
589
|
+
logs.setGlobalLoggerProvider(this.loggerProvider);
|
|
590
|
+
actionsCore.debug(`OpenTelemetry export enabled to ${otelCore.getStringFromEnv("OTEL_EXPORTER_OTLP_ENDPOINT")}`);
|
|
591
|
+
} catch (e) {
|
|
592
|
+
this.tracerProvider = void 0;
|
|
593
|
+
this.loggerProvider = void 0;
|
|
594
|
+
this.idGenerator = void 0;
|
|
595
|
+
actionsCore.debug(`Failed to start OpenTelemetry export, continuing without it: ${stringifyError(e)}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Start the span that {@link newTraceparent} announced.
|
|
600
|
+
*
|
|
601
|
+
* A workflow job runs each Action as a process of its own.
|
|
602
|
+
* Thus a span that covers more than one Action can only start in one of them.
|
|
603
|
+
* The Action that announces such a span makes its identity known first, and
|
|
604
|
+
* starts the span itself last, in the process that runs at the end.
|
|
605
|
+
* The spans that already point at that identity then find their parent.
|
|
606
|
+
*
|
|
607
|
+
* The span starts at `startTime`, which is the moment of the announcement.
|
|
608
|
+
* It is a child of the span in `parentContext`, and a root span if that
|
|
609
|
+
* context holds no span.
|
|
610
|
+
*
|
|
611
|
+
* Returns undefined if the export is off, or if `traceparent` does not name a
|
|
612
|
+
* usable span.
|
|
613
|
+
*/
|
|
614
|
+
startAnnouncedSpan(name, traceparent, startTime, parentContext = otelApi.ROOT_CONTEXT) {
|
|
615
|
+
const generator = this.idGenerator;
|
|
616
|
+
const spanContext = otelApi.trace.getSpanContext(contextFromTraceparent(traceparent));
|
|
617
|
+
if (generator === void 0 || this.tracerProvider === void 0 || spanContext === void 0 || !otelApi.isSpanContextValid(spanContext)) return;
|
|
618
|
+
const tracer = this.tracerProvider.getTracer(SCOPE_NAME, "1.0");
|
|
619
|
+
try {
|
|
620
|
+
generator.pin(spanContext.traceId, spanContext.spanId);
|
|
621
|
+
return tracer.startSpan(name, { startTime }, parentContext);
|
|
622
|
+
} finally {
|
|
623
|
+
generator.unpin();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Flush buffered spans and logs and tear the SDK down.
|
|
628
|
+
*
|
|
629
|
+
* Never throws and never hangs: the Action calls this on its way out, so a
|
|
630
|
+
* broken or slow collector must not be able to fail or stall the workflow.
|
|
631
|
+
*/
|
|
632
|
+
async shutdown() {
|
|
633
|
+
const providers = [this.tracerProvider, this.loggerProvider].flatMap((p) => p ?? []);
|
|
634
|
+
if (providers.length === 0) return;
|
|
635
|
+
try {
|
|
636
|
+
await withTimeout(Promise.all(providers.map(async (p) => p.shutdown())), SHUTDOWN_TIMEOUT_MS);
|
|
637
|
+
} catch (e) {
|
|
638
|
+
actionsCore.debug(`Error flushing OpenTelemetry data: ${stringifyError(e)}`);
|
|
639
|
+
} finally {
|
|
640
|
+
this.tracerProvider = void 0;
|
|
641
|
+
this.loggerProvider = void 0;
|
|
642
|
+
this.idGenerator = void 0;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
/**
|
|
647
|
+
* The tracer for this library. Returns a no-op tracer until {@link
|
|
648
|
+
* Telemetry.start} has run, so this is always safe to call.
|
|
649
|
+
*/
|
|
650
|
+
function getTracer() {
|
|
651
|
+
return otelApi.trace.getTracer(SCOPE_NAME, "1.0");
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* The logger for this library. Returns a no-op logger until {@link
|
|
655
|
+
* Telemetry.start} has run, so this is always safe to call.
|
|
656
|
+
*/
|
|
657
|
+
function getLogger() {
|
|
658
|
+
return logs.getLogger(SCOPE_NAME, "1.0");
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Emit a log record at `level`, correlated to whatever span is currently
|
|
662
|
+
* active.
|
|
663
|
+
*/
|
|
664
|
+
function emitLogRecord(level, message, attributes) {
|
|
665
|
+
getLogger().emit({
|
|
666
|
+
severityNumber: SEVERITY[level],
|
|
667
|
+
severityText: level.toUpperCase(),
|
|
668
|
+
body: message,
|
|
669
|
+
attributes,
|
|
670
|
+
context: otelApi.context.active()
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Serialize a span as a W3C `traceparent` header value, suitable for stashing
|
|
675
|
+
* in the Action's state or handing to a child process.
|
|
676
|
+
*
|
|
677
|
+
* Returns undefined when telemetry is disabled, since the no-op span's context
|
|
678
|
+
* is all zeroes and would not be a valid parent.
|
|
679
|
+
*/
|
|
680
|
+
function traceparentOf(span) {
|
|
681
|
+
if (span === void 0 || !otelApi.isSpanContextValid(span.spanContext())) return;
|
|
682
|
+
const carrier = {};
|
|
683
|
+
PROPAGATOR.inject(otelApi.trace.setSpan(otelApi.ROOT_CONTEXT, span), carrier, otelApi.defaultTextMapSetter);
|
|
684
|
+
return carrier["traceparent"];
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Make the identity of a span, but do not start the span.
|
|
688
|
+
*
|
|
689
|
+
* Announce the result to whatever must point at the span before it starts:
|
|
690
|
+
* a different process, or a request this process makes too early to record.
|
|
691
|
+
* Start the span itself with {@link Telemetry.startAnnouncedSpan}.
|
|
692
|
+
*
|
|
693
|
+
* The span is in the trace of `parent`, or in a new trace of its own if there
|
|
694
|
+
* is no usable parent.
|
|
695
|
+
* A new trace is sampled, because a process that only forwards an identity
|
|
696
|
+
* cannot ask the sampler, and an unsampled parent would discard the work of
|
|
697
|
+
* each process that joins.
|
|
698
|
+
*/
|
|
699
|
+
function newTraceparent(parent) {
|
|
700
|
+
const parentContext = otelApi.trace.getSpanContext(contextFromTraceparent(parent));
|
|
701
|
+
if (parentContext !== void 0 && otelApi.isSpanContextValid(parentContext)) {
|
|
702
|
+
const flags = parentContext.traceFlags.toString(16).padStart(2, "0");
|
|
703
|
+
return `00-${parentContext.traceId}-${randomHex(8)}-${flags}`;
|
|
704
|
+
}
|
|
705
|
+
return `00-${randomHex(16)}-${randomHex(8)}-01`;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* The W3C trace context headers of the operation in progress, for an outgoing
|
|
709
|
+
* HTTP request.
|
|
710
|
+
*
|
|
711
|
+
* Put these headers on the request.
|
|
712
|
+
* The service that answers it can then put its own work in this trace.
|
|
713
|
+
*
|
|
714
|
+
* The headers describe the span that is active now.
|
|
715
|
+
* When no span is active yet -- a request the Action makes before it starts a
|
|
716
|
+
* span of its own -- they describe the span that `$TRACEPARENT` names, which is
|
|
717
|
+
* the span the Action announced, or the span of the workflow job.
|
|
718
|
+
*
|
|
719
|
+
* The result is empty when the export is off.
|
|
720
|
+
* A no-op span's context is all zeroes, and is not a valid parent.
|
|
721
|
+
*/
|
|
722
|
+
function traceContextHeaders() {
|
|
723
|
+
const active = otelApi.context.active();
|
|
724
|
+
const context = otelApi.trace.getSpanContext(active) === void 0 ? contextFromTraceparent(process.env["TRACEPARENT"]) : active;
|
|
725
|
+
const carrier = {};
|
|
726
|
+
PROPAGATOR.inject(context, carrier, otelApi.defaultTextMapSetter);
|
|
727
|
+
return carrier;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Rebuild a Context from a W3C `traceparent` value, so a span started in one
|
|
731
|
+
* process can parent spans started in another. Falls back to the root context
|
|
732
|
+
* when `traceparent` is absent or unparseable.
|
|
733
|
+
*/
|
|
734
|
+
function contextFromTraceparent(traceparent) {
|
|
735
|
+
if (traceparent === void 0 || traceparent === "") return otelApi.ROOT_CONTEXT;
|
|
736
|
+
return PROPAGATOR.extract(otelApi.ROOT_CONTEXT, { traceparent }, otelApi.defaultTextMapGetter);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Mark `span` as failed and attach the exception to it.
|
|
740
|
+
*/
|
|
741
|
+
function recordSpanError(span, error) {
|
|
742
|
+
span.recordException(error instanceof Error ? error : new Error(stringifyError(error)));
|
|
743
|
+
span.setStatus({
|
|
744
|
+
code: otelApi.SpanStatusCode.ERROR,
|
|
745
|
+
message: stringifyError(error)
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Run `fn` inside a new active span, ending the span when it settles and
|
|
750
|
+
* marking it failed if it throws. The error is always re-thrown: this records,
|
|
751
|
+
* it does not swallow.
|
|
752
|
+
*/
|
|
753
|
+
async function withSpan(name, fn, attributes) {
|
|
754
|
+
return await getTracer().startActiveSpan(name, { attributes }, async (span) => {
|
|
755
|
+
try {
|
|
756
|
+
return await fn(span);
|
|
757
|
+
} catch (e) {
|
|
758
|
+
recordSpanError(span, e);
|
|
759
|
+
throw e;
|
|
760
|
+
} finally {
|
|
761
|
+
span.end();
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
/** A random ID of `bytes` bytes, in the lowercase hex the W3C format uses. */
|
|
766
|
+
function randomHex(bytes) {
|
|
767
|
+
return randomBytes(bytes).toString("hex");
|
|
768
|
+
}
|
|
769
|
+
/** Reject if `promise` has not settled within `timeoutMs`. */
|
|
770
|
+
async function withTimeout(promise, timeoutMs) {
|
|
771
|
+
let timer;
|
|
772
|
+
try {
|
|
773
|
+
return await Promise.race([promise, new Promise((_resolve, reject) => {
|
|
774
|
+
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
775
|
+
})]);
|
|
776
|
+
} finally {
|
|
777
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
//#endregion
|
|
466
781
|
//#region src/ids-host.ts
|
|
467
782
|
/**
|
|
468
783
|
* @packageDocumentation
|
|
@@ -500,6 +815,7 @@ var IdsHost = class {
|
|
|
500
815
|
actionsCore.info(`Retrying after error ${error.code}, retry #: ${retryCount}`);
|
|
501
816
|
}],
|
|
502
817
|
beforeRequest: [async (options) => {
|
|
818
|
+
for (const [name, value] of Object.entries(traceContextHeaders())) options.headers[name] = value;
|
|
503
819
|
const currentUrl = options.url;
|
|
504
820
|
if (this.isUrlSubjectToDynamicUrls(currentUrl)) {
|
|
505
821
|
const newUrl = new URL(currentUrl);
|
|
@@ -544,6 +860,13 @@ var IdsHost = class {
|
|
|
544
860
|
if (url === void 0) return new URL(DEFAULT_IDS_HOST);
|
|
545
861
|
return url;
|
|
546
862
|
}
|
|
863
|
+
/**
|
|
864
|
+
* The diagnostics endpoint of the current backend.
|
|
865
|
+
*
|
|
866
|
+
* This library reports nothing there: its telemetry is OpenTelemetry. The
|
|
867
|
+
* URL is for the programs an Action runs, which have diagnostics of their
|
|
868
|
+
* own.
|
|
869
|
+
*/
|
|
547
870
|
async getDiagnosticsUrl() {
|
|
548
871
|
if (this.runtimeDiagnosticsUrl === "") return;
|
|
549
872
|
if (this.runtimeDiagnosticsUrl !== "-" && this.runtimeDiagnosticsUrl !== void 0) try {
|
|
@@ -728,6 +1051,85 @@ const getStringOrUndefined = (name) => {
|
|
|
728
1051
|
else return value;
|
|
729
1052
|
};
|
|
730
1053
|
//#endregion
|
|
1054
|
+
//#region src/log.ts
|
|
1055
|
+
/**
|
|
1056
|
+
* @packageDocumentation
|
|
1057
|
+
* Logging that tees to both the GitHub Actions console and OpenTelemetry.
|
|
1058
|
+
*
|
|
1059
|
+
* These are drop-in replacements for the `@actions/core` logging functions.
|
|
1060
|
+
* Every call still writes to the workflow log exactly as it did before -- the
|
|
1061
|
+
* user-visible output is unchanged -- and additionally emits an OpenTelemetry
|
|
1062
|
+
* LogRecord correlated to the currently active span.
|
|
1063
|
+
*
|
|
1064
|
+
* When telemetry is disabled the OpenTelemetry half is a no-op, so these
|
|
1065
|
+
* behave identically to calling `@actions/core` directly.
|
|
1066
|
+
*/
|
|
1067
|
+
var log_exports = /* @__PURE__ */ __exportAll({
|
|
1068
|
+
debug: () => debug,
|
|
1069
|
+
error: () => error,
|
|
1070
|
+
group: () => group,
|
|
1071
|
+
info: () => info,
|
|
1072
|
+
notice: () => notice,
|
|
1073
|
+
setFailed: () => setFailed,
|
|
1074
|
+
warning: () => warning
|
|
1075
|
+
});
|
|
1076
|
+
function tee(level, message, attributes) {
|
|
1077
|
+
const text = typeof message === "string" ? message : stringifyError(message);
|
|
1078
|
+
emitLogRecord(level, text, attributes);
|
|
1079
|
+
return text;
|
|
1080
|
+
}
|
|
1081
|
+
/**
|
|
1082
|
+
* Write a debug message. Only visible in the workflow log when the user has
|
|
1083
|
+
* enabled step debug logging, but always exported to OpenTelemetry.
|
|
1084
|
+
*/
|
|
1085
|
+
function debug(message, attributes) {
|
|
1086
|
+
actionsCore.debug(tee("debug", message, attributes));
|
|
1087
|
+
}
|
|
1088
|
+
/** Write an informational message to the workflow log. */
|
|
1089
|
+
function info(message, attributes) {
|
|
1090
|
+
actionsCore.info(tee("info", message, attributes));
|
|
1091
|
+
}
|
|
1092
|
+
/** Write a notice annotation to the workflow log. */
|
|
1093
|
+
function notice(message, properties, attributes) {
|
|
1094
|
+
tee("notice", message, attributes);
|
|
1095
|
+
actionsCore.notice(message, properties);
|
|
1096
|
+
}
|
|
1097
|
+
/** Write a warning annotation to the workflow log. */
|
|
1098
|
+
function warning(message, properties, attributes) {
|
|
1099
|
+
tee("warning", message, attributes);
|
|
1100
|
+
actionsCore.warning(message, properties);
|
|
1101
|
+
}
|
|
1102
|
+
/** Write an error annotation to the workflow log. */
|
|
1103
|
+
function error(message, properties, attributes) {
|
|
1104
|
+
tee("error", message, attributes);
|
|
1105
|
+
actionsCore.error(message, properties);
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Fail the workflow step, recording the reason as an OpenTelemetry error log.
|
|
1109
|
+
*/
|
|
1110
|
+
function setFailed(message, attributes) {
|
|
1111
|
+
tee("error", message, attributes);
|
|
1112
|
+
actionsCore.setFailed(message);
|
|
1113
|
+
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Run `fn` inside both a collapsible group in the workflow log and an active
|
|
1116
|
+
* OpenTelemetry span of the same name.
|
|
1117
|
+
*
|
|
1118
|
+
* This is the replacement for a `startGroup`/`endGroup` pair: the group closes
|
|
1119
|
+
* and the span ends even if `fn` throws, and a throwing `fn` marks the span
|
|
1120
|
+
* failed before re-throwing.
|
|
1121
|
+
*/
|
|
1122
|
+
async function group(name, fn, attributes) {
|
|
1123
|
+
return await withSpan(name, async () => {
|
|
1124
|
+
actionsCore.startGroup(name);
|
|
1125
|
+
try {
|
|
1126
|
+
return await fn();
|
|
1127
|
+
} finally {
|
|
1128
|
+
actionsCore.endGroup();
|
|
1129
|
+
}
|
|
1130
|
+
}, attributes);
|
|
1131
|
+
}
|
|
1132
|
+
//#endregion
|
|
731
1133
|
//#region src/platform.ts
|
|
732
1134
|
/**
|
|
733
1135
|
* @packageDocumentation
|
|
@@ -805,40 +1207,52 @@ function noisilyGetInput(suffix, legacyPrefix) {
|
|
|
805
1207
|
* @packageDocumentation
|
|
806
1208
|
* Determinate Systems' TypeScript library for creating GitHub Actions logic.
|
|
807
1209
|
*/
|
|
808
|
-
const
|
|
809
|
-
const
|
|
810
|
-
const
|
|
811
|
-
const
|
|
812
|
-
const
|
|
813
|
-
const
|
|
814
|
-
const
|
|
815
|
-
const
|
|
816
|
-
const
|
|
817
|
-
const
|
|
818
|
-
const
|
|
819
|
-
const
|
|
820
|
-
const
|
|
821
|
-
const
|
|
822
|
-
const
|
|
823
|
-
const
|
|
824
|
-
const
|
|
825
|
-
const
|
|
826
|
-
const
|
|
827
|
-
const
|
|
828
|
-
const
|
|
829
|
-
const
|
|
1210
|
+
const EVENT_IDS_FAILOVER = "detsys.ids_failover";
|
|
1211
|
+
const EVENT_PREFLIGHT_REQUIRE_NIX_DENIED = "detsys.preflight_require_nix_denied";
|
|
1212
|
+
const EVENT_REQUEST_TIMEOUT = "detsys.request_timeout";
|
|
1213
|
+
const EVENT_STORE_IDENTITY_FAILED = "detsys.store_identity_failed";
|
|
1214
|
+
const ATTR_PROJECT = "detsys.project";
|
|
1215
|
+
const ATTR_IDS_PROJECT = "detsys.ids_project";
|
|
1216
|
+
const ATTR_EXECUTION_PHASE = "detsys.execution_phase";
|
|
1217
|
+
const ATTR_CROSS_PHASE_ID = "detsys.cross_phase_id";
|
|
1218
|
+
const ATTR_ANONYMOUS_ID = "detsys.anonymous_id";
|
|
1219
|
+
const ATTR_CORRELATION_SOURCE = "detsys.correlation_source";
|
|
1220
|
+
const ATTR_ARCH_OS = "detsys.arch_os";
|
|
1221
|
+
const ATTR_NIX_SYSTEM = "detsys.nix_system";
|
|
1222
|
+
const ATTR_FEATURE_PREFIX = "detsys.feature.";
|
|
1223
|
+
const ATTR_GITHUB_EVENT_NAME = "detsys.github.event_name";
|
|
1224
|
+
const ATTR_GITHUB_ACTION_REPOSITORY = "detsys.github.action_repository";
|
|
1225
|
+
const ATTR_GITHUB_REPOSITORY_HASH = "detsys.github.repository_hash";
|
|
1226
|
+
const ATTR_GITHUB_ORGANIZATION_HASH = "detsys.github.organization_hash";
|
|
1227
|
+
const ATTR_GITHUB_WORKFLOW_HASH = "detsys.github.workflow_hash";
|
|
1228
|
+
const ATTR_GITHUB_WORKFLOW_JOB_HASH = "detsys.github.workflow_job_hash";
|
|
1229
|
+
const ATTR_GITHUB_WORKFLOW_RUN_HASH = "detsys.github.workflow_run_hash";
|
|
1230
|
+
const ATTR_GITHUB_WORKFLOW_RUN_DIFFERENTIATOR_HASH = "detsys.github.workflow_run_differentiator_hash";
|
|
1231
|
+
const ATTR_ARTIFACT_NAME = "detsys.artifact.name";
|
|
1232
|
+
const ATTR_ARTIFACT_FETCH_SUFFIX = "detsys.artifact.fetch_suffix";
|
|
1233
|
+
const ATTR_ARTIFACT_CACHE_HIT = "detsys.artifact.cache_hit";
|
|
1234
|
+
const ATTR_SOURCE_URL = "detsys.source.url";
|
|
1235
|
+
const ATTR_SOURCE_ETAG = "detsys.source.etag";
|
|
1236
|
+
const ATTR_SOURCE_CHECKSUMS_SHA256 = "detsys.source.checksums_sha256";
|
|
1237
|
+
const ATTR_NIX_LOCATION = "detsys.nix.location";
|
|
1238
|
+
const ATTR_NIX_VERSION = "detsys.nix.version";
|
|
1239
|
+
const ATTR_NIX_STORE_TRUST = "detsys.nix.store_trust";
|
|
1240
|
+
const ATTR_NIX_STORE_VERSION = "detsys.nix.store_version";
|
|
1241
|
+
const ATTR_NIX_STORE_CHECK_METHOD = "detsys.nix.store_check_method";
|
|
1242
|
+
const ATTR_NIX_STORE_CHECK_ERROR = "detsys.nix.store_check_error";
|
|
1243
|
+
const ATTR_ATTACHMENT_NAME = "detsys.attachment.name";
|
|
1244
|
+
const ATTR_ATTACHMENT_PATH = "detsys.attachment.path";
|
|
830
1245
|
const STATE_KEY_EXECUTION_PHASE = "detsys_action_execution_phase";
|
|
831
1246
|
const STATE_KEY_NIX_NOT_FOUND = "detsys_action_nix_not_found";
|
|
832
1247
|
const STATE_NOT_FOUND = "not-found";
|
|
833
1248
|
const STATE_KEY_CROSS_PHASE_ID = "detsys_cross_phase_id";
|
|
834
|
-
const
|
|
835
|
-
const
|
|
1249
|
+
const STATE_KEY_TRACEPARENT = "detsys_otel_traceparent";
|
|
1250
|
+
const STATE_KEY_JOB_TRACEPARENT = "detsys_otel_job_traceparent";
|
|
1251
|
+
const STATE_KEY_JOB_SPAN_START = "detsys_otel_job_span_start";
|
|
1252
|
+
const ENV_TRACEPARENT = "TRACEPARENT";
|
|
1253
|
+
const SPAN_JOB = "github_actions_job";
|
|
1254
|
+
const SPAN_CHECK_IN = "check_in";
|
|
836
1255
|
const CHECK_IN_ENDPOINT_TIMEOUT_MS = 1e3;
|
|
837
|
-
const PROGRAM_NAME_CRASH_DENY_LIST = [
|
|
838
|
-
"nix-expr-tests",
|
|
839
|
-
"nix-store-tests",
|
|
840
|
-
"nix-util-tests"
|
|
841
|
-
];
|
|
842
1256
|
const determinateStateDir = "/var/lib/determinate";
|
|
843
1257
|
const determinateIdentityFile = path.join(determinateStateDir, "identity.json");
|
|
844
1258
|
const isRoot = typeof process.geteuid === "function" && process.geteuid() === 0;
|
|
@@ -861,14 +1275,14 @@ async function sudoWriteCorrelationHashes(hashes) {
|
|
|
861
1275
|
const buffer = Buffer.from(hashes);
|
|
862
1276
|
const code = await exec$1.exec("sudo", ["tee", determinateIdentityFile], {
|
|
863
1277
|
input: buffer,
|
|
864
|
-
outStream: createWriteStream("/dev/null")
|
|
1278
|
+
outStream: nodeFs.createWriteStream("/dev/null")
|
|
865
1279
|
});
|
|
866
1280
|
if (code !== 0) throw new Error(`sudo tee exit: ${code}`);
|
|
867
1281
|
}
|
|
868
1282
|
/** Writes correlation hashes to the Determinate state directory, escalating if necessary */
|
|
869
1283
|
async function writeCorrelationHashes(hashes) {
|
|
870
1284
|
await ensureDeterminateStateDir();
|
|
871
|
-
if (isRoot) await fs.writeFile(determinateIdentityFile, hashes, "utf-8");
|
|
1285
|
+
if (isRoot) await fs$1.writeFile(determinateIdentityFile, hashes, "utf-8");
|
|
872
1286
|
else return sudoWriteCorrelationHashes(hashes);
|
|
873
1287
|
}
|
|
874
1288
|
var DetSysAction = class {
|
|
@@ -881,6 +1295,7 @@ var DetSysAction = class {
|
|
|
881
1295
|
constructor(actionOptions) {
|
|
882
1296
|
this.actionOptions = makeOptionsConfident(actionOptions);
|
|
883
1297
|
this.idsHost = new IdsHost(this.actionOptions.idsProjectName, actionOptions.diagnosticsSuffix, process.env["INPUT_DIAGNOSTIC-ENDPOINT"], getNumberOrUndefined("timeout-request"));
|
|
1298
|
+
this.telemetry = new Telemetry();
|
|
884
1299
|
this.exceptionAttachments = /* @__PURE__ */ new Map();
|
|
885
1300
|
this.nixStoreTrust = "unknown";
|
|
886
1301
|
this.strictMode = getBool("_internal-strict-mode");
|
|
@@ -889,54 +1304,33 @@ var DetSysAction = class {
|
|
|
889
1304
|
process.env["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = void 0;
|
|
890
1305
|
}
|
|
891
1306
|
this.features = {};
|
|
892
|
-
this.
|
|
893
|
-
this.events = [];
|
|
1307
|
+
this.pendingAttributes = {};
|
|
894
1308
|
this.getCrossPhaseId();
|
|
895
|
-
this.collectBacktraceSetup();
|
|
896
|
-
this.facts = {
|
|
897
|
-
$lib: "idslib",
|
|
898
|
-
$lib_version: pkgVersion,
|
|
899
|
-
project: this.actionOptions.name,
|
|
900
|
-
ids_project: this.actionOptions.idsProjectName
|
|
901
|
-
};
|
|
902
|
-
for (const [target, env] of [
|
|
903
|
-
["github_action_ref", "GITHUB_ACTION_REF"],
|
|
904
|
-
["github_action_repository", "GITHUB_ACTION_REPOSITORY"],
|
|
905
|
-
["github_event_name", "GITHUB_EVENT_NAME"],
|
|
906
|
-
["$os", "RUNNER_OS"],
|
|
907
|
-
["arch", "RUNNER_ARCH"]
|
|
908
|
-
]) {
|
|
909
|
-
const value = process.env[env];
|
|
910
|
-
if (value) this.facts[target] = value;
|
|
911
|
-
}
|
|
912
1309
|
this.identity = identify();
|
|
913
1310
|
this.archOs = getArchOs();
|
|
914
1311
|
this.nixSystem = getNixPlatform(this.archOs);
|
|
915
|
-
this.
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
if (details.name !== "unknown") this.addFact(FACT_OS, details.name);
|
|
920
|
-
if (details.version !== "unknown") this.addFact(FACT_OS_VERSION, details.version);
|
|
921
|
-
}).catch((e) => {
|
|
1312
|
+
this.systemDetails = getDetails().then((details) => ({
|
|
1313
|
+
name: details.name,
|
|
1314
|
+
version: details.version
|
|
1315
|
+
})).catch((e) => {
|
|
922
1316
|
actionsCore.debug(`Failure getting platform details: ${stringifyError$1(e)}`);
|
|
923
1317
|
});
|
|
924
1318
|
this.executionPhase = this.determineExecutionPhase();
|
|
925
|
-
this.facts.execution_phase = this.executionPhase;
|
|
926
1319
|
if (this.actionOptions.fetchStyle === "gh-env-style") this.architectureFetchSuffix = this.archOs;
|
|
927
1320
|
else if (this.actionOptions.fetchStyle === "nix-style") this.architectureFetchSuffix = this.nixSystem;
|
|
928
1321
|
else if (this.actionOptions.fetchStyle === "universal") this.architectureFetchSuffix = "universal";
|
|
929
1322
|
else throw new Error(`fetchStyle ${this.actionOptions.fetchStyle} is not a valid style`);
|
|
930
1323
|
this.sourceParameters = constructSourceParameters(this.actionOptions.legacySourcePrefix);
|
|
931
|
-
this.recordEvent(`begin_${this.executionPhase}`);
|
|
932
1324
|
}
|
|
933
1325
|
/**
|
|
934
|
-
* Attach a file to the
|
|
1326
|
+
* Attach a file to the telemetry for this run, to be emitted if the Action
|
|
1327
|
+
* fails.
|
|
935
1328
|
*
|
|
936
1329
|
* The file at `location` doesn't need to exist when stapleFile is called.
|
|
937
1330
|
*
|
|
938
|
-
*
|
|
939
|
-
*
|
|
1331
|
+
* Each attachment becomes one OpenTelemetry log record, correlated to the
|
|
1332
|
+
* phase's span: the file's contents as the body if it can be read, the
|
|
1333
|
+
* reason it could not be read otherwise.
|
|
940
1334
|
*/
|
|
941
1335
|
stapleFile(name, location) {
|
|
942
1336
|
this.exceptionAttachments.set(name, location);
|
|
@@ -954,9 +1348,29 @@ var DetSysAction = class {
|
|
|
954
1348
|
const tmpDir = process.env["RUNNER_TEMP"] || tmpdir();
|
|
955
1349
|
return path.join(tmpDir, `${this.actionOptions.name}-${randomUUID()}`);
|
|
956
1350
|
}
|
|
957
|
-
|
|
958
|
-
|
|
1351
|
+
/**
|
|
1352
|
+
* Describe this run with an attribute.
|
|
1353
|
+
*
|
|
1354
|
+
* The attribute lands on the phase's root span, not on whichever span
|
|
1355
|
+
* happens to be active, because it describes the run as a whole. Set it
|
|
1356
|
+
* whenever the value becomes known: attributes set before the span opens
|
|
1357
|
+
* are replayed onto it.
|
|
1358
|
+
*
|
|
1359
|
+
* Namespace your keys, as OpenTelemetry expects: `detsys.nix.version`, not
|
|
1360
|
+
* `nix_version`.
|
|
1361
|
+
*/
|
|
1362
|
+
setAttribute(key, value) {
|
|
1363
|
+
if (this.phaseSpan === void 0) this.pendingAttributes[key] = value;
|
|
1364
|
+
else this.phaseSpan.setAttribute(key, value);
|
|
959
1365
|
}
|
|
1366
|
+
/**
|
|
1367
|
+
* The diagnostics endpoint for the programs this Action runs, such as
|
|
1368
|
+
* `nix-installer` and `magic-nix-cache`.
|
|
1369
|
+
*
|
|
1370
|
+
* This library reports nothing there. Its own telemetry is OpenTelemetry;
|
|
1371
|
+
* see {@link getTelemetryEnvironment} for putting a child process's
|
|
1372
|
+
* telemetry in this run's trace.
|
|
1373
|
+
*/
|
|
960
1374
|
async getDiagnosticsUrl() {
|
|
961
1375
|
return await this.idsHost.getDiagnosticsUrl();
|
|
962
1376
|
}
|
|
@@ -974,20 +1388,17 @@ var DetSysAction = class {
|
|
|
974
1388
|
getCorrelationHashes() {
|
|
975
1389
|
return this.identity;
|
|
976
1390
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
...Object.fromEntries(Object.entries(this.featureEventMetadata).map(([name, variant]) => [`$feature/${name}`, variant]))
|
|
989
|
-
}
|
|
990
|
-
});
|
|
1391
|
+
/**
|
|
1392
|
+
* Record that something happened, as a span event.
|
|
1393
|
+
*
|
|
1394
|
+
* The event lands on whichever span is active, so that it sits on the
|
|
1395
|
+
* operation that produced it, and on the phase's root span when there is no
|
|
1396
|
+
* nested span in progress.
|
|
1397
|
+
*
|
|
1398
|
+
* Namespace your attribute keys, as OpenTelemetry expects.
|
|
1399
|
+
*/
|
|
1400
|
+
addEvent(name, attributes) {
|
|
1401
|
+
(otelApi.trace.getActiveSpan() ?? this.phaseSpan)?.addEvent(name, attributes);
|
|
991
1402
|
}
|
|
992
1403
|
/**
|
|
993
1404
|
* Unpacks the closure returned by `fetchArtifact()`, imports the
|
|
@@ -997,7 +1408,7 @@ var DetSysAction = class {
|
|
|
997
1408
|
async unpackClosure(bin) {
|
|
998
1409
|
const artifact = await this.fetchArtifact();
|
|
999
1410
|
const { stdout } = await promisify(exec)(`cat "${artifact}" | xz -d | nix-store --import`);
|
|
1000
|
-
return `${stdout.split(os
|
|
1411
|
+
return `${stdout.split(os.EOL).at(-2)}/bin/${bin}`;
|
|
1001
1412
|
}
|
|
1002
1413
|
/**
|
|
1003
1414
|
* Fetches the executable at the URL determined by the `source-*` inputs and
|
|
@@ -1005,7 +1416,7 @@ var DetSysAction = class {
|
|
|
1005
1416
|
*/
|
|
1006
1417
|
async fetchExecutable() {
|
|
1007
1418
|
const binaryPath = await this.fetchArtifact();
|
|
1008
|
-
await chmod(binaryPath, constants.S_IXUSR | constants.S_IXGRP);
|
|
1419
|
+
await chmod(binaryPath, nodeFs.constants.S_IXUSR | nodeFs.constants.S_IXGRP);
|
|
1009
1420
|
return binaryPath;
|
|
1010
1421
|
}
|
|
1011
1422
|
get isMain() {
|
|
@@ -1015,63 +1426,219 @@ var DetSysAction = class {
|
|
|
1015
1426
|
return this.executionPhase === "post";
|
|
1016
1427
|
}
|
|
1017
1428
|
async executeAsync() {
|
|
1429
|
+
const phaseStartTime = /* @__PURE__ */ new Date();
|
|
1018
1430
|
try {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
await
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1431
|
+
this.announceJobTrace(phaseStartTime);
|
|
1432
|
+
await this.startTelemetry();
|
|
1433
|
+
this.startPhaseSpan(phaseStartTime);
|
|
1434
|
+
await this.withPhaseSpanActive(async () => {
|
|
1435
|
+
await withSpan(SPAN_CHECK_IN, async () => {
|
|
1436
|
+
await this.checkIn();
|
|
1437
|
+
});
|
|
1438
|
+
const correlationHashes = JSON.stringify(this.getCorrelationHashes());
|
|
1439
|
+
process.env.DETSYS_CORRELATION = correlationHashes;
|
|
1440
|
+
try {
|
|
1441
|
+
await writeCorrelationHashes(correlationHashes);
|
|
1442
|
+
} catch (error) {
|
|
1443
|
+
this.addEvent(EVENT_STORE_IDENTITY_FAILED, { [semconv.ATTR_EXCEPTION_MESSAGE]: stringifyError$1(error) });
|
|
1444
|
+
}
|
|
1445
|
+
if (!await this.preflightRequireNix()) {
|
|
1446
|
+
this.addEvent(EVENT_PREFLIGHT_REQUIRE_NIX_DENIED);
|
|
1447
|
+
return;
|
|
1448
|
+
} else {
|
|
1449
|
+
await this.preflightNixStoreInfo();
|
|
1450
|
+
await this.preflightNixVersion();
|
|
1451
|
+
this.setAttribute(ATTR_NIX_STORE_TRUST, this.nixStoreTrust);
|
|
1452
|
+
}
|
|
1453
|
+
if (this.isMain) {
|
|
1454
|
+
await this.main();
|
|
1455
|
+
await this.preflightNixVersion();
|
|
1456
|
+
} else if (this.isPost) await this.post();
|
|
1457
|
+
});
|
|
1041
1458
|
} catch (e) {
|
|
1042
|
-
this.addFact(FACT_ENDED_WITH_EXCEPTION, true);
|
|
1043
1459
|
const reportable = stringifyError$1(e);
|
|
1044
|
-
this.
|
|
1045
|
-
if (this.isPost)
|
|
1046
|
-
else
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
const buf = await doGzip(readFileSync(filePath));
|
|
1051
|
-
exceptionContext.set(`staple_value_${attachmentLabel}`, buf.toString("base64"));
|
|
1052
|
-
} catch (innerError) {
|
|
1053
|
-
exceptionContext.set(`staple_failure_${attachmentLabel}`, stringifyError$1(innerError));
|
|
1054
|
-
}
|
|
1055
|
-
this.recordEvent(EVENT_EXCEPTION, Object.fromEntries(exceptionContext));
|
|
1460
|
+
if (this.phaseSpan !== void 0) recordSpanError(this.phaseSpan, e);
|
|
1461
|
+
if (this.isPost) warning(reportable);
|
|
1462
|
+
else setFailed(reportable);
|
|
1463
|
+
await this.withPhaseSpanActive(async () => {
|
|
1464
|
+
await this.emitAttachments();
|
|
1465
|
+
});
|
|
1056
1466
|
} finally {
|
|
1057
|
-
if (this.isPost) await this.collectBacktraces();
|
|
1058
1467
|
await this.complete();
|
|
1059
1468
|
}
|
|
1060
1469
|
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Run `fn` with the phase's root span as the active span, so anything it
|
|
1472
|
+
* starts is parented into this phase's trace.
|
|
1473
|
+
*/
|
|
1474
|
+
async withPhaseSpanActive(fn) {
|
|
1475
|
+
const span = this.phaseSpan;
|
|
1476
|
+
if (span === void 0) return await fn();
|
|
1477
|
+
return await otelApi.context.with(otelApi.trace.setSpan(otelApi.context.active(), span), fn);
|
|
1478
|
+
}
|
|
1479
|
+
/**
|
|
1480
|
+
* Start the OpenTelemetry export.
|
|
1481
|
+
*
|
|
1482
|
+
* All runs export their data.
|
|
1483
|
+
* To stop the export, set `OTEL_SDK_DISABLED` to `true`, or set
|
|
1484
|
+
* `OTEL_EXPORTER_OTLP_ENDPOINT` to an empty value.
|
|
1485
|
+
* The SDK then does not start.
|
|
1486
|
+
* The OpenTelemetry API stays in its no-op state.
|
|
1487
|
+
* Each span and log record then does nothing.
|
|
1488
|
+
* Thus the call sites do not test if the export is on.
|
|
1489
|
+
*/
|
|
1490
|
+
async startTelemetry() {
|
|
1491
|
+
this.telemetry.start({
|
|
1492
|
+
serviceName: `${this.actionOptions.name}-action`,
|
|
1493
|
+
serviceVersion: process.env["GITHUB_ACTION_REF"],
|
|
1494
|
+
resourceAttributes: await this.telemetryResourceAttributes()
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
/**
|
|
1498
|
+
* Put every Action of this workflow job in one trace.
|
|
1499
|
+
*
|
|
1500
|
+
* A job runs each Action as a process of its own.
|
|
1501
|
+
* Thus the Actions can only agree on a trace through the job's environment.
|
|
1502
|
+
* The first Action to run makes the identity of the job's span and exports it
|
|
1503
|
+
* as `$TRACEPARENT`.
|
|
1504
|
+
* Each later step finds it there: the other Actions, and the programs the
|
|
1505
|
+
* workflow runs, such as Nix.
|
|
1506
|
+
*
|
|
1507
|
+
* The span itself starts and ends in the post phase of the Action that
|
|
1508
|
+
* announced it.
|
|
1509
|
+
* GitHub Actions runs the post phases in the reverse of the order of the main
|
|
1510
|
+
* phases, thus that phase is the last one of the job.
|
|
1511
|
+
* The span then covers the whole job.
|
|
1512
|
+
* See {@link endJobSpan}.
|
|
1513
|
+
*
|
|
1514
|
+
* A `$TRACEPARENT` that is already set belongs to an earlier Action, or to the
|
|
1515
|
+
* system that started the workflow.
|
|
1516
|
+
* Do not change it, and join that trace.
|
|
1517
|
+
*/
|
|
1518
|
+
announceJobTrace(startTime) {
|
|
1519
|
+
if (!this.isMain || !exportEnabled()) return;
|
|
1520
|
+
if (process.env[ENV_TRACEPARENT]) return;
|
|
1521
|
+
const traceparent = newTraceparent();
|
|
1522
|
+
actionsCore.exportVariable(ENV_TRACEPARENT, traceparent);
|
|
1523
|
+
actionsCore.saveState(STATE_KEY_JOB_TRACEPARENT, traceparent);
|
|
1524
|
+
actionsCore.saveState(STATE_KEY_JOB_SPAN_START, `${startTime.getTime()}`);
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* End the job's span, if this Action is the one that announced it.
|
|
1528
|
+
*
|
|
1529
|
+
* The span also starts here.
|
|
1530
|
+
* A span belongs to the process that ends it, and the process that made the
|
|
1531
|
+
* announcement stopped long ago.
|
|
1532
|
+
* See {@link announceJobTrace}.
|
|
1533
|
+
*/
|
|
1534
|
+
endJobSpan() {
|
|
1535
|
+
if (!this.isPost) return;
|
|
1536
|
+
const traceparent = actionsCore.getState(STATE_KEY_JOB_TRACEPARENT);
|
|
1537
|
+
if (traceparent === "") return;
|
|
1538
|
+
const startTime = parseInt(actionsCore.getState(STATE_KEY_JOB_SPAN_START), 10);
|
|
1539
|
+
this.telemetry.startAnnouncedSpan(SPAN_JOB, traceparent, new Date(Number.isFinite(startTime) ? startTime : Date.now()))?.end();
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Start the root span of this execution phase.
|
|
1543
|
+
*
|
|
1544
|
+
* The span starts at the moment the phase did, and thus covers the start of
|
|
1545
|
+
* the SDK, which comes before it.
|
|
1546
|
+
*
|
|
1547
|
+
* `main` and `post` are separate processes.
|
|
1548
|
+
* Thus the main phase saves the identity of its span in the Action's state,
|
|
1549
|
+
* and the post phase makes its span a child of it.
|
|
1550
|
+
* A `$TRACEPARENT` in the environment is the span of the workflow job, or of
|
|
1551
|
+
* the system that started the workflow.
|
|
1552
|
+
*/
|
|
1553
|
+
startPhaseSpan(startTime) {
|
|
1554
|
+
if (!this.telemetry.enabled) return;
|
|
1555
|
+
const parent = actionsCore.getState(STATE_KEY_TRACEPARENT) || process.env[ENV_TRACEPARENT] || void 0;
|
|
1556
|
+
const span = getTracer().startSpan(`${this.actionOptions.name}:${this.executionPhase}`, { startTime }, contextFromTraceparent(parent));
|
|
1557
|
+
span.setAttributes(this.pendingAttributes);
|
|
1558
|
+
this.pendingAttributes = {};
|
|
1559
|
+
const traceparent = traceparentOf(span);
|
|
1560
|
+
if (traceparent !== void 0) {
|
|
1561
|
+
process.env[ENV_TRACEPARENT] = traceparent;
|
|
1562
|
+
if (this.isMain) actionsCore.saveState(STATE_KEY_TRACEPARENT, traceparent);
|
|
1563
|
+
}
|
|
1564
|
+
this.phaseSpan = span;
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* The stable, run-scoped attributes attached to every span and log record.
|
|
1568
|
+
*
|
|
1569
|
+
* The correlation data here is hashed and does not identify a repository,
|
|
1570
|
+
* an organization, or a person.
|
|
1571
|
+
*/
|
|
1572
|
+
async telemetryResourceAttributes() {
|
|
1573
|
+
const details = await this.systemDetails;
|
|
1574
|
+
return {
|
|
1575
|
+
[semconvIncubating.ATTR_OS_TYPE]: osType(),
|
|
1576
|
+
[semconvIncubating.ATTR_HOST_ARCH]: hostArch(),
|
|
1577
|
+
...details?.name === void 0 || details.name === "unknown" ? {} : { [semconvIncubating.ATTR_OS_NAME]: details.name },
|
|
1578
|
+
...details?.version === void 0 || details.version === "unknown" ? {} : { [semconvIncubating.ATTR_OS_VERSION]: details.version },
|
|
1579
|
+
[ATTR_PROJECT]: this.actionOptions.name,
|
|
1580
|
+
[ATTR_IDS_PROJECT]: this.actionOptions.idsProjectName,
|
|
1581
|
+
[ATTR_EXECUTION_PHASE]: this.executionPhase,
|
|
1582
|
+
[ATTR_CROSS_PHASE_ID]: this.getCrossPhaseId(),
|
|
1583
|
+
[ATTR_ANONYMOUS_ID]: this.identity.$anon_distinct_id,
|
|
1584
|
+
[ATTR_CORRELATION_SOURCE]: this.identity.correlation_source,
|
|
1585
|
+
[ATTR_ARCH_OS]: this.archOs,
|
|
1586
|
+
[ATTR_NIX_SYSTEM]: this.nixSystem,
|
|
1587
|
+
[ATTR_GITHUB_EVENT_NAME]: process.env["GITHUB_EVENT_NAME"],
|
|
1588
|
+
[ATTR_GITHUB_ACTION_REPOSITORY]: process.env["GITHUB_ACTION_REPOSITORY"],
|
|
1589
|
+
[ATTR_GITHUB_REPOSITORY_HASH]: this.identity.github_repository_hash,
|
|
1590
|
+
[ATTR_GITHUB_ORGANIZATION_HASH]: this.identity.$groups["github_organization"],
|
|
1591
|
+
[ATTR_GITHUB_WORKFLOW_HASH]: this.identity.github_workflow_hash,
|
|
1592
|
+
[ATTR_GITHUB_WORKFLOW_JOB_HASH]: this.identity.github_workflow_job_hash,
|
|
1593
|
+
[ATTR_GITHUB_WORKFLOW_RUN_HASH]: this.identity.github_workflow_run_hash,
|
|
1594
|
+
[ATTR_GITHUB_WORKFLOW_RUN_DIFFERENTIATOR_HASH]: this.identity.github_workflow_run_differentiator_hash
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
/**
|
|
1598
|
+
* The W3C `traceparent` identifying the span currently in progress.
|
|
1599
|
+
*
|
|
1600
|
+
* Hand this to a child process -- as `$TRACEPARENT` -- so that its own
|
|
1601
|
+
* OpenTelemetry data joins this Action's trace. Returns undefined when
|
|
1602
|
+
* OpenTelemetry export is disabled for this run.
|
|
1603
|
+
*/
|
|
1604
|
+
getTraceparent() {
|
|
1605
|
+
return traceparentOf(otelApi.trace.getActiveSpan() ?? this.phaseSpan);
|
|
1606
|
+
}
|
|
1607
|
+
/**
|
|
1608
|
+
* The environment variables that let a child process add data to this
|
|
1609
|
+
* Action's trace: the current `$TRACEPARENT` and the OTLP export settings.
|
|
1610
|
+
*
|
|
1611
|
+
* Add these variables to the environment of each child process to trace.
|
|
1612
|
+
* A child that inherits this process's environment already has the OTLP
|
|
1613
|
+
* settings; only `$TRACEPARENT` changes as the run proceeds.
|
|
1614
|
+
*
|
|
1615
|
+
* The result is empty if the OpenTelemetry export is off.
|
|
1616
|
+
* Thus it is always safe to add them.
|
|
1617
|
+
*/
|
|
1618
|
+
async getTelemetryEnvironment() {
|
|
1619
|
+
if (!this.telemetry.enabled) return {};
|
|
1620
|
+
const environment = otlpExportEnvironment();
|
|
1621
|
+
const traceparent = this.getTraceparent();
|
|
1622
|
+
if (traceparent !== void 0) environment[ENV_TRACEPARENT] = traceparent;
|
|
1623
|
+
return environment;
|
|
1624
|
+
}
|
|
1061
1625
|
async getClient() {
|
|
1062
1626
|
return await this.idsHost.getGot((incitingError, prevUrl, nextUrl) => {
|
|
1063
1627
|
this.recordPlausibleTimeout(incitingError);
|
|
1064
|
-
this.
|
|
1065
|
-
|
|
1066
|
-
|
|
1628
|
+
this.addEvent(EVENT_IDS_FAILOVER, {
|
|
1629
|
+
"detsys.ids.previous_url": prevUrl.toString(),
|
|
1630
|
+
"detsys.ids.next_url": nextUrl.toString()
|
|
1067
1631
|
});
|
|
1068
1632
|
});
|
|
1069
1633
|
}
|
|
1634
|
+
/**
|
|
1635
|
+
* Check in, and tell the user about the incidents and the maintenance the
|
|
1636
|
+
* check-in reports.
|
|
1637
|
+
*/
|
|
1070
1638
|
async checkIn() {
|
|
1071
1639
|
const checkin = await this.requestCheckIn();
|
|
1072
1640
|
if (checkin === void 0) return;
|
|
1073
1641
|
this.features = checkin.options;
|
|
1074
|
-
for (const [key, feature] of Object.entries(this.features)) this.featureEventMetadata[key] = feature.variant;
|
|
1075
1642
|
const impactSymbol = /* @__PURE__ */ new Map([
|
|
1076
1643
|
["none", "⚪"],
|
|
1077
1644
|
["maintenance", "🛠️"],
|
|
@@ -1092,24 +1659,60 @@ var DetSysAction = class {
|
|
|
1092
1659
|
}
|
|
1093
1660
|
}
|
|
1094
1661
|
}
|
|
1662
|
+
/**
|
|
1663
|
+
* The variant of a feature flag this run resolved, if the check-in returned
|
|
1664
|
+
* one.
|
|
1665
|
+
*
|
|
1666
|
+
* Each variant this Action asks for becomes an attribute of the run, under
|
|
1667
|
+
* `detsys.feature.`, so the telemetry can be sliced by the flags that
|
|
1668
|
+
* changed what the run did.
|
|
1669
|
+
*/
|
|
1095
1670
|
getFeature(name) {
|
|
1096
1671
|
if (!this.features.hasOwnProperty(name)) return;
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1672
|
+
const feature = this.features[name];
|
|
1673
|
+
this.setAttribute(`${ATTR_FEATURE_PREFIX}${name}`, feature.variant);
|
|
1674
|
+
return feature;
|
|
1675
|
+
}
|
|
1676
|
+
/**
|
|
1677
|
+
* The person properties the check-in evaluates feature flags against.
|
|
1678
|
+
*
|
|
1679
|
+
* These names are the flag-targeting contract with the feature flag
|
|
1680
|
+
* service, which is why they keep their `$`-prefixed spelling. They are not
|
|
1681
|
+
* telemetry: nothing here is reported anywhere. The telemetry for this run
|
|
1682
|
+
* is OpenTelemetry, and it names the same values the way OpenTelemetry
|
|
1683
|
+
* does.
|
|
1684
|
+
*/
|
|
1685
|
+
async checkInPersonProperties() {
|
|
1686
|
+
const properties = {
|
|
1687
|
+
ci: "github",
|
|
1688
|
+
$lib: "idslib",
|
|
1689
|
+
$lib_version: "1.0",
|
|
1690
|
+
$app_name: `${this.actionOptions.name}/action`,
|
|
1691
|
+
project: this.actionOptions.name,
|
|
1692
|
+
ids_project: this.actionOptions.idsProjectName,
|
|
1693
|
+
arch_os: this.archOs,
|
|
1694
|
+
nix_system: this.nixSystem,
|
|
1695
|
+
execution_phase: this.executionPhase
|
|
1696
|
+
};
|
|
1697
|
+
for (const [target, variable] of [
|
|
1698
|
+
["github_action_ref", "GITHUB_ACTION_REF"],
|
|
1699
|
+
["github_action_repository", "GITHUB_ACTION_REPOSITORY"],
|
|
1700
|
+
["github_event_name", "GITHUB_EVENT_NAME"],
|
|
1701
|
+
["$os", "RUNNER_OS"],
|
|
1702
|
+
["arch", "RUNNER_ARCH"]
|
|
1703
|
+
]) {
|
|
1704
|
+
const value = process.env[variable];
|
|
1705
|
+
if (value) properties[target] = value;
|
|
1706
|
+
}
|
|
1707
|
+
const details = await this.systemDetails;
|
|
1708
|
+
if (details !== void 0) {
|
|
1709
|
+
if (details.name !== "unknown") properties.$os = details.name;
|
|
1710
|
+
if (details.version !== "unknown") properties.$os_version = details.version;
|
|
1711
|
+
}
|
|
1712
|
+
return {
|
|
1713
|
+
...properties,
|
|
1714
|
+
...this.identity
|
|
1715
|
+
};
|
|
1113
1716
|
}
|
|
1114
1717
|
/**
|
|
1115
1718
|
* Check in to install.determinate.systems, to accomplish three things:
|
|
@@ -1128,11 +1731,7 @@ var DetSysAction = class {
|
|
|
1128
1731
|
distinct_id: this.identity.$anon_distinct_id,
|
|
1129
1732
|
anon_distinct_id: this.identity.$anon_distinct_id,
|
|
1130
1733
|
groups: this.identity.$groups,
|
|
1131
|
-
person_properties:
|
|
1132
|
-
ci: "github",
|
|
1133
|
-
...this.identity,
|
|
1134
|
-
...this.facts
|
|
1135
|
-
}
|
|
1734
|
+
person_properties: await this.checkInPersonProperties()
|
|
1136
1735
|
};
|
|
1137
1736
|
return await (await this.getClient()).post(checkInUrl, {
|
|
1138
1737
|
json: props,
|
|
@@ -1147,12 +1746,12 @@ var DetSysAction = class {
|
|
|
1147
1746
|
}
|
|
1148
1747
|
recordPlausibleTimeout(e) {
|
|
1149
1748
|
if (e instanceof TimeoutError && "timings" in e && "request" in e) {
|
|
1150
|
-
const
|
|
1151
|
-
|
|
1152
|
-
|
|
1749
|
+
const attributes = {
|
|
1750
|
+
[semconv.ATTR_URL_FULL]: e.request.requestUrl?.toString(),
|
|
1751
|
+
[semconv.ATTR_HTTP_REQUEST_RESEND_COUNT]: e.request.retryCount
|
|
1153
1752
|
};
|
|
1154
|
-
for (const [key, value] of Object.entries(e.timings.phases)) if (Number.isFinite(value))
|
|
1155
|
-
this.
|
|
1753
|
+
for (const [key, value] of Object.entries(e.timings.phases)) if (Number.isFinite(value)) attributes[`detsys.http.timing.${key}`] = value;
|
|
1754
|
+
this.addEvent(EVENT_REQUEST_TIMEOUT, attributes);
|
|
1156
1755
|
}
|
|
1157
1756
|
}
|
|
1158
1757
|
/**
|
|
@@ -1170,49 +1769,54 @@ var DetSysAction = class {
|
|
|
1170
1769
|
async fetchArtifact() {
|
|
1171
1770
|
const sourceBinary = getStringOrNull("source-binary");
|
|
1172
1771
|
if (sourceBinary !== null && sourceBinary !== "") {
|
|
1173
|
-
|
|
1772
|
+
debug(`Using the provided source binary at ${sourceBinary}`);
|
|
1174
1773
|
return sourceBinary;
|
|
1175
1774
|
}
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1775
|
+
return await withSpan("fetch_artifact", async (span) => {
|
|
1776
|
+
const expectedArtifactHash = await this.resolveExpectedArtifactHash();
|
|
1777
|
+
actionsCore.startGroup(`Downloading ${this.actionOptions.name} for ${this.architectureFetchSuffix}`);
|
|
1778
|
+
try {
|
|
1779
|
+
info(`Fetching from ${await this.getSourceUrl()}`);
|
|
1780
|
+
const correlatedUrl = await this.getSourceUrl();
|
|
1781
|
+
correlatedUrl.searchParams.set("ci", "github");
|
|
1782
|
+
correlatedUrl.searchParams.set("correlation", JSON.stringify(this.identity));
|
|
1783
|
+
const versionCheckup = await (await this.getClient()).head(correlatedUrl);
|
|
1784
|
+
if (versionCheckup.headers.etag) {
|
|
1785
|
+
const v = versionCheckup.headers.etag;
|
|
1786
|
+
this.setAttribute(ATTR_SOURCE_ETAG, v);
|
|
1787
|
+
debug(`Checking the tool cache for ${await this.getSourceUrl()} at ${v}`);
|
|
1788
|
+
const cached = await this.getCachedVersion(v, expectedArtifactHash);
|
|
1789
|
+
if (cached) {
|
|
1790
|
+
span.setAttribute(ATTR_ARTIFACT_CACHE_HIT, true);
|
|
1791
|
+
debug(`Tool cache hit.`);
|
|
1792
|
+
await this.verifyArtifactHash(cached, expectedArtifactHash);
|
|
1793
|
+
return cached;
|
|
1794
|
+
}
|
|
1194
1795
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1796
|
+
span.setAttribute(ATTR_ARTIFACT_CACHE_HIT, false);
|
|
1797
|
+
debug(`No match from the cache, re-fetching from the redirect: ${versionCheckup.url}`);
|
|
1798
|
+
const destFile = this.getTemporaryName();
|
|
1799
|
+
const fetchStream = await this.downloadFile(new URL(versionCheckup.url), destFile);
|
|
1800
|
+
await this.verifyArtifactHash(destFile, expectedArtifactHash);
|
|
1801
|
+
if (fetchStream.response?.headers.etag) {
|
|
1802
|
+
const v = fetchStream.response.headers.etag;
|
|
1803
|
+
try {
|
|
1804
|
+
await this.saveCachedVersion(v, destFile, expectedArtifactHash);
|
|
1805
|
+
} catch (e) {
|
|
1806
|
+
debug(`Error caching the artifact: ${stringifyError$1(e)}`);
|
|
1807
|
+
}
|
|
1207
1808
|
}
|
|
1809
|
+
return destFile;
|
|
1810
|
+
} catch (e) {
|
|
1811
|
+
this.recordPlausibleTimeout(e);
|
|
1812
|
+
throw e;
|
|
1813
|
+
} finally {
|
|
1814
|
+
actionsCore.endGroup();
|
|
1208
1815
|
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
this.
|
|
1212
|
-
|
|
1213
|
-
} finally {
|
|
1214
|
-
actionsCore.endGroup();
|
|
1215
|
-
}
|
|
1816
|
+
}, {
|
|
1817
|
+
[ATTR_ARTIFACT_NAME]: this.actionOptions.name,
|
|
1818
|
+
[ATTR_ARTIFACT_FETCH_SUFFIX]: this.architectureFetchSuffix
|
|
1819
|
+
});
|
|
1216
1820
|
}
|
|
1217
1821
|
/**
|
|
1218
1822
|
* Read the `source-checksums-url` and `source-checksums-sha256` inputs and,
|
|
@@ -1228,7 +1832,7 @@ var DetSysAction = class {
|
|
|
1228
1832
|
if (checksumsUrl === null || checksumsSha256 === null) throw new Error("`source-checksums-url` and `source-checksums-sha256` must be set together");
|
|
1229
1833
|
assertChecksumSourceIsPinned(this.sourceParameters);
|
|
1230
1834
|
const expectedFileHash = checksumsSha256.toLowerCase();
|
|
1231
|
-
this.
|
|
1835
|
+
this.setAttribute(ATTR_SOURCE_CHECKSUMS_SHA256, expectedFileHash);
|
|
1232
1836
|
const parsedUrl = new URL(checksumsUrl);
|
|
1233
1837
|
const safeUrl = parsedUrl.origin + parsedUrl.pathname;
|
|
1234
1838
|
actionsCore.info(`Fetching checksums file from ${safeUrl}`);
|
|
@@ -1257,13 +1861,16 @@ var DetSysAction = class {
|
|
|
1257
1861
|
if (this.strictMode) actionsCore.setFailed(`strict mode failure: ${msg}`);
|
|
1258
1862
|
}
|
|
1259
1863
|
async downloadFile(url, destination) {
|
|
1864
|
+
return await withSpan("download_file", async () => this.download(url, destination));
|
|
1865
|
+
}
|
|
1866
|
+
async download(url, destination) {
|
|
1260
1867
|
const client = await this.getClient();
|
|
1261
1868
|
return new Promise((resolve, reject) => {
|
|
1262
1869
|
let writeStream;
|
|
1263
1870
|
let failed = false;
|
|
1264
1871
|
const retry = (stream) => {
|
|
1265
1872
|
if (writeStream) writeStream.destroy();
|
|
1266
|
-
writeStream = createWriteStream(destination, {
|
|
1873
|
+
writeStream = nodeFs.createWriteStream(destination, {
|
|
1267
1874
|
encoding: "binary",
|
|
1268
1875
|
mode: 493
|
|
1269
1876
|
});
|
|
@@ -1283,8 +1890,10 @@ var DetSysAction = class {
|
|
|
1283
1890
|
});
|
|
1284
1891
|
}
|
|
1285
1892
|
async complete() {
|
|
1286
|
-
this.
|
|
1287
|
-
|
|
1893
|
+
this.phaseSpan?.end();
|
|
1894
|
+
this.phaseSpan = void 0;
|
|
1895
|
+
this.endJobSpan();
|
|
1896
|
+
await this.telemetry.shutdown();
|
|
1288
1897
|
}
|
|
1289
1898
|
async getCheckInUrl() {
|
|
1290
1899
|
const checkInUrl = await this.idsHost.getDynamicRootUrl();
|
|
@@ -1295,7 +1904,7 @@ var DetSysAction = class {
|
|
|
1295
1904
|
async getSourceUrl() {
|
|
1296
1905
|
const p = this.sourceParameters;
|
|
1297
1906
|
if (p.url) {
|
|
1298
|
-
this.
|
|
1907
|
+
this.setAttribute(ATTR_SOURCE_URL, p.url);
|
|
1299
1908
|
return new URL(p.url);
|
|
1300
1909
|
}
|
|
1301
1910
|
const fetchUrl = await this.idsHost.getRootUrl();
|
|
@@ -1306,7 +1915,7 @@ var DetSysAction = class {
|
|
|
1306
1915
|
else if (p.revision) fetchUrl.pathname += `/rev/${p.revision}`;
|
|
1307
1916
|
else fetchUrl.pathname += `/stable`;
|
|
1308
1917
|
fetchUrl.pathname += `/${this.architectureFetchSuffix}`;
|
|
1309
|
-
this.
|
|
1918
|
+
this.setAttribute(ATTR_SOURCE_URL, fetchUrl.toString());
|
|
1310
1919
|
return fetchUrl;
|
|
1311
1920
|
}
|
|
1312
1921
|
cacheKey(version, expectedHash) {
|
|
@@ -1315,179 +1924,188 @@ var DetSysAction = class {
|
|
|
1315
1924
|
return `determinatesystem-${this.actionOptions.name}-${this.architectureFetchSuffix}-${cleanedVersion}${hashSuffix}`;
|
|
1316
1925
|
}
|
|
1317
1926
|
async getCachedVersion(version, expectedHash) {
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
this.
|
|
1327
|
-
|
|
1927
|
+
return await withSpan("artifact_cache_restore", async (span) => {
|
|
1928
|
+
const startCwd = process.cwd();
|
|
1929
|
+
try {
|
|
1930
|
+
const tempDir = this.getTemporaryName();
|
|
1931
|
+
await mkdir(tempDir);
|
|
1932
|
+
process.chdir(tempDir);
|
|
1933
|
+
process.env.GITHUB_WORKSPACE_BACKUP = process.env.GITHUB_WORKSPACE;
|
|
1934
|
+
delete process.env.GITHUB_WORKSPACE;
|
|
1935
|
+
if (await actionsCache.restoreCache([this.actionOptions.name], this.cacheKey(version, expectedHash), [], void 0, true)) {
|
|
1936
|
+
span.setAttribute(ATTR_ARTIFACT_CACHE_HIT, true);
|
|
1937
|
+
return `${tempDir}/${this.actionOptions.name}`;
|
|
1938
|
+
}
|
|
1939
|
+
span.setAttribute(ATTR_ARTIFACT_CACHE_HIT, false);
|
|
1940
|
+
return;
|
|
1941
|
+
} finally {
|
|
1942
|
+
process.env.GITHUB_WORKSPACE = process.env.GITHUB_WORKSPACE_BACKUP;
|
|
1943
|
+
delete process.env.GITHUB_WORKSPACE_BACKUP;
|
|
1944
|
+
process.chdir(startCwd);
|
|
1328
1945
|
}
|
|
1329
|
-
|
|
1330
|
-
return;
|
|
1331
|
-
} finally {
|
|
1332
|
-
process.env.GITHUB_WORKSPACE = process.env.GITHUB_WORKSPACE_BACKUP;
|
|
1333
|
-
delete process.env.GITHUB_WORKSPACE_BACKUP;
|
|
1334
|
-
process.chdir(startCwd);
|
|
1335
|
-
}
|
|
1946
|
+
});
|
|
1336
1947
|
}
|
|
1337
1948
|
async saveCachedVersion(version, toolPath, expectedHash) {
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
collectBacktraceSetup() {
|
|
1355
|
-
if (!process.env.DETSYS_BACKTRACE_COLLECTOR) {
|
|
1356
|
-
actionsCore.exportVariable("DETSYS_BACKTRACE_COLLECTOR", this.getCrossPhaseId());
|
|
1357
|
-
actionsCore.saveState(STATE_BACKTRACE_START_TIMESTAMP, Date.now());
|
|
1358
|
-
}
|
|
1949
|
+
return await withSpan("artifact_cache_persist", async () => {
|
|
1950
|
+
const startCwd = process.cwd();
|
|
1951
|
+
try {
|
|
1952
|
+
const tempDir = this.getTemporaryName();
|
|
1953
|
+
await mkdir(tempDir);
|
|
1954
|
+
process.chdir(tempDir);
|
|
1955
|
+
await copyFile(toolPath, `${tempDir}/${this.actionOptions.name}`);
|
|
1956
|
+
process.env.GITHUB_WORKSPACE_BACKUP = process.env.GITHUB_WORKSPACE;
|
|
1957
|
+
delete process.env.GITHUB_WORKSPACE;
|
|
1958
|
+
await actionsCache.saveCache([this.actionOptions.name], this.cacheKey(version, expectedHash), void 0, true);
|
|
1959
|
+
} finally {
|
|
1960
|
+
process.env.GITHUB_WORKSPACE = process.env.GITHUB_WORKSPACE_BACKUP;
|
|
1961
|
+
delete process.env.GITHUB_WORKSPACE_BACKUP;
|
|
1962
|
+
process.chdir(startCwd);
|
|
1963
|
+
}
|
|
1964
|
+
});
|
|
1359
1965
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1966
|
+
/**
|
|
1967
|
+
* Emit the files `stapleFile` collected, as log records correlated to this
|
|
1968
|
+
* phase's span. The Action has already failed by the time this runs.
|
|
1969
|
+
*/
|
|
1970
|
+
async emitAttachments() {
|
|
1971
|
+
for (const [name, location] of this.exceptionAttachments) {
|
|
1972
|
+
const attributes = {
|
|
1973
|
+
[ATTR_ATTACHMENT_NAME]: name,
|
|
1974
|
+
[ATTR_ATTACHMENT_PATH]: location.toString()
|
|
1975
|
+
};
|
|
1976
|
+
try {
|
|
1977
|
+
emitLogRecord("error", await readFile(location, "utf-8"), attributes);
|
|
1978
|
+
} catch (innerError) {
|
|
1979
|
+
emitLogRecord("error", `Attachment unavailable`, {
|
|
1980
|
+
...attributes,
|
|
1981
|
+
[semconv.ATTR_EXCEPTION_MESSAGE]: stringifyError$1(innerError)
|
|
1982
|
+
});
|
|
1983
|
+
}
|
|
1368
1984
|
}
|
|
1369
1985
|
}
|
|
1370
1986
|
async preflightRequireNix() {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
const
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1987
|
+
return await withSpan("preflight_require_nix", async () => {
|
|
1988
|
+
let nixLocation;
|
|
1989
|
+
const pathParts = (process.env["PATH"] || "").split(":");
|
|
1990
|
+
for (const location of pathParts) {
|
|
1991
|
+
const candidateNix = path.join(location, "nix");
|
|
1992
|
+
try {
|
|
1993
|
+
await fs$1.access(candidateNix, fs$1.constants.X_OK);
|
|
1994
|
+
debug(`Found Nix at ${candidateNix}`);
|
|
1995
|
+
nixLocation = candidateNix;
|
|
1996
|
+
break;
|
|
1997
|
+
} catch {
|
|
1998
|
+
actionsCore.debug(`Nix not at ${candidateNix}`);
|
|
1999
|
+
}
|
|
1382
2000
|
}
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
2001
|
+
this.setAttribute(ATTR_NIX_LOCATION, nixLocation || "");
|
|
2002
|
+
if (this.actionOptions.requireNix === "ignore") return true;
|
|
2003
|
+
if (actionsCore.getState(STATE_KEY_NIX_NOT_FOUND) === STATE_NOT_FOUND) return false;
|
|
2004
|
+
if (nixLocation !== void 0) return true;
|
|
2005
|
+
actionsCore.saveState(STATE_KEY_NIX_NOT_FOUND, STATE_NOT_FOUND);
|
|
2006
|
+
switch (this.actionOptions.requireNix) {
|
|
2007
|
+
case "fail":
|
|
2008
|
+
setFailed(["This action can only be used when Nix is installed.", "Add `- uses: DeterminateSystems/determinate-nix-action@v3` earlier in your workflow."].join(" "));
|
|
2009
|
+
break;
|
|
2010
|
+
case "warn": warning(["This action is in no-op mode because Nix is not installed.", "Add `- uses: DeterminateSystems/determinate-nix-action@v3` earlier in your workflow."].join(" "));
|
|
2011
|
+
}
|
|
2012
|
+
return false;
|
|
2013
|
+
});
|
|
1396
2014
|
}
|
|
1397
2015
|
async preflightNixStoreInfo() {
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
output = "";
|
|
1406
|
-
await exec$1.exec("nix", [
|
|
1407
|
-
"store",
|
|
1408
|
-
"info",
|
|
1409
|
-
"--json"
|
|
1410
|
-
], options);
|
|
1411
|
-
this.addFact(FACT_NIX_STORE_CHECK_METHOD, "info");
|
|
1412
|
-
} catch {
|
|
2016
|
+
return await withSpan("preflight_nix_store_info", async (span) => {
|
|
2017
|
+
let output = "";
|
|
2018
|
+
const options = {};
|
|
2019
|
+
options.silent = true;
|
|
2020
|
+
options.listeners = { stdout: (data) => {
|
|
2021
|
+
output += data.toString();
|
|
2022
|
+
} };
|
|
1413
2023
|
try {
|
|
1414
2024
|
output = "";
|
|
1415
2025
|
await exec$1.exec("nix", [
|
|
1416
2026
|
"store",
|
|
1417
|
-
"
|
|
2027
|
+
"info",
|
|
1418
2028
|
"--json"
|
|
1419
2029
|
], options);
|
|
1420
|
-
this.
|
|
2030
|
+
this.setAttribute(ATTR_NIX_STORE_CHECK_METHOD, "info");
|
|
1421
2031
|
} catch {
|
|
1422
|
-
|
|
1423
|
-
|
|
2032
|
+
try {
|
|
2033
|
+
output = "";
|
|
2034
|
+
await exec$1.exec("nix", [
|
|
2035
|
+
"store",
|
|
2036
|
+
"ping",
|
|
2037
|
+
"--json"
|
|
2038
|
+
], options);
|
|
2039
|
+
this.setAttribute(ATTR_NIX_STORE_CHECK_METHOD, "ping");
|
|
2040
|
+
} catch {
|
|
2041
|
+
this.setAttribute(ATTR_NIX_STORE_CHECK_METHOD, "none");
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
1424
2044
|
}
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
2045
|
+
try {
|
|
2046
|
+
const parsed = JSON.parse(output);
|
|
2047
|
+
if (parsed.trusted === true || parsed.trusted === 1) this.nixStoreTrust = "trusted";
|
|
2048
|
+
else if (parsed.trusted === false || parsed.trusted === 0) this.nixStoreTrust = "untrusted";
|
|
2049
|
+
else if (parsed.trusted !== void 0) this.setAttribute(ATTR_NIX_STORE_CHECK_ERROR, `Mysterious trusted value: ${JSON.stringify(parsed.trusted)}`);
|
|
2050
|
+
this.setAttribute(ATTR_NIX_STORE_VERSION, JSON.stringify(parsed.version));
|
|
2051
|
+
} catch (e) {
|
|
2052
|
+
this.setAttribute(ATTR_NIX_STORE_CHECK_ERROR, stringifyError$1(e));
|
|
2053
|
+
}
|
|
2054
|
+
span.setAttribute(ATTR_NIX_STORE_TRUST, this.nixStoreTrust);
|
|
2055
|
+
});
|
|
1435
2056
|
}
|
|
1436
2057
|
async preflightNixVersion() {
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
if (diagnosticsUrl === void 0) {
|
|
1447
|
-
actionsCore.debug("Diagnostics are disabled. Not sending the following events:");
|
|
1448
|
-
actionsCore.debug(JSON.stringify(this.events, void 0, 2));
|
|
1449
|
-
return;
|
|
1450
|
-
}
|
|
1451
|
-
const batch = {
|
|
1452
|
-
sent_at: /* @__PURE__ */ new Date(),
|
|
1453
|
-
batch: this.events
|
|
1454
|
-
};
|
|
1455
|
-
try {
|
|
1456
|
-
await (await this.getClient()).post(diagnosticsUrl, {
|
|
1457
|
-
json: batch,
|
|
1458
|
-
timeout: { request: DIAGNOSTIC_ENDPOINT_TIMEOUT_MS }
|
|
1459
|
-
});
|
|
1460
|
-
} catch (err) {
|
|
1461
|
-
this.recordPlausibleTimeout(err);
|
|
1462
|
-
actionsCore.debug(`Error submitting diagnostics event to ${diagnosticsUrl}: ${stringifyError$1(err)}`);
|
|
1463
|
-
}
|
|
1464
|
-
this.events = [];
|
|
2058
|
+
return await withSpan("preflight_nix_version", async (span) => {
|
|
2059
|
+
let output = "unknown";
|
|
2060
|
+
try {
|
|
2061
|
+
({stdout: output} = await exec$1.getExecOutput("nix", ["--version"], { silent: true }));
|
|
2062
|
+
output = output.trim() || "unknown";
|
|
2063
|
+
} catch {}
|
|
2064
|
+
this.setAttribute(ATTR_NIX_VERSION, output);
|
|
2065
|
+
span.setAttribute(ATTR_NIX_VERSION, output);
|
|
2066
|
+
});
|
|
1465
2067
|
}
|
|
1466
2068
|
};
|
|
1467
2069
|
function stringifyError$1(error) {
|
|
1468
2070
|
return error instanceof Error || typeof error == "string" ? error.toString() : JSON.stringify(error);
|
|
1469
2071
|
}
|
|
2072
|
+
/**
|
|
2073
|
+
* The runner's operating system, as `os.type` spells it.
|
|
2074
|
+
*/
|
|
2075
|
+
function osType() {
|
|
2076
|
+
switch (platform) {
|
|
2077
|
+
case "win32": return semconvIncubating.OS_TYPE_VALUE_WINDOWS;
|
|
2078
|
+
case "darwin": return semconvIncubating.OS_TYPE_VALUE_DARWIN;
|
|
2079
|
+
case "linux": return semconvIncubating.OS_TYPE_VALUE_LINUX;
|
|
2080
|
+
default: return platform;
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
/**
|
|
2084
|
+
* The runner's architecture, as `host.arch` spells it.
|
|
2085
|
+
*/
|
|
2086
|
+
function hostArch() {
|
|
2087
|
+
switch (arch) {
|
|
2088
|
+
case "x64": return semconvIncubating.HOST_ARCH_VALUE_AMD64;
|
|
2089
|
+
case "arm64": return semconvIncubating.HOST_ARCH_VALUE_ARM64;
|
|
2090
|
+
case "ia32": return semconvIncubating.HOST_ARCH_VALUE_X86;
|
|
2091
|
+
case "arm": return semconvIncubating.HOST_ARCH_VALUE_ARM32;
|
|
2092
|
+
default: return arch;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
1470
2095
|
function makeOptionsConfident(actionOptions) {
|
|
1471
2096
|
const idsProjectName = actionOptions.idsProjectName ?? actionOptions.name;
|
|
1472
2097
|
const finalOpts = {
|
|
1473
2098
|
name: actionOptions.name,
|
|
1474
2099
|
idsProjectName,
|
|
1475
|
-
eventPrefix: actionOptions.eventPrefix || "action:",
|
|
1476
2100
|
fetchStyle: actionOptions.fetchStyle,
|
|
1477
2101
|
legacySourcePrefix: actionOptions.legacySourcePrefix,
|
|
1478
|
-
requireNix: actionOptions.requireNix
|
|
1479
|
-
binaryNamePrefixes: actionOptions.binaryNamePrefixes ?? [
|
|
1480
|
-
"nix",
|
|
1481
|
-
"determinate-nixd",
|
|
1482
|
-
actionOptions.name
|
|
1483
|
-
],
|
|
1484
|
-
binaryNamesDenyList: actionOptions.binaryNamesDenyList ?? PROGRAM_NAME_CRASH_DENY_LIST
|
|
2102
|
+
requireNix: actionOptions.requireNix
|
|
1485
2103
|
};
|
|
1486
2104
|
actionsCore.debug("idslib options:");
|
|
1487
2105
|
actionsCore.debug(JSON.stringify(finalOpts, void 0, 2));
|
|
1488
2106
|
return finalOpts;
|
|
1489
2107
|
}
|
|
1490
2108
|
//#endregion
|
|
1491
|
-
export { DetSysAction, IdsHost, inputs_exports as inputs, platform_exports as platform, stringifyError };
|
|
2109
|
+
export { DetSysAction, IdsHost, SCOPE_NAME, contextFromTraceparent, getLogger, getTracer, inputs_exports as inputs, log_exports as log, platform_exports as platform, recordSpanError, stringifyError, traceContextHeaders, traceparentOf, withSpan };
|
|
1492
2110
|
|
|
1493
2111
|
//# sourceMappingURL=index.mjs.map
|