Skip to content

Commit 74bc395

Browse files
author
Cole Kennedy
committed
.
1 parent a8e2925 commit 74bc395

File tree

1 file changed

+49
-180
lines changed

1 file changed

+49
-180
lines changed

index.js

+49-180
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const core = require("@actions/core");
22
const exec = require("@actions/exec");
3-
const { exit } = require("process");
43
const process = require("process");
54
const fs = require("fs");
65
const os = require("os");
@@ -10,41 +9,22 @@ const unzipper = require("unzipper");
109
const yaml = require("js-yaml");
1110
const tc = require("@actions/tool-cache");
1211

13-
// Handle signals to ensure clean exit
14-
process.on('SIGINT', () => {
15-
console.log('Action wrapper received SIGINT, exiting...');
16-
process.exit(0);
17-
});
18-
19-
process.on('SIGTERM', () => {
20-
console.log('Action wrapper received SIGTERM, exiting...');
21-
process.exit(0);
22-
});
12+
process.on('SIGINT', () => process.exit(0));
13+
process.on('SIGTERM', () => process.exit(0));
2314

24-
// Set an absolute maximum timeout for the entire action (30 minutes)
2515
const MAX_ACTION_DURATION_MS = 30 * 60 * 1000;
26-
const actionTimeoutId = setTimeout(() => {
27-
core.warning(`Action timed out after ${MAX_ACTION_DURATION_MS / 60000} minutes. This is likely a bug in the action wrapper. Forcing exit.`);
28-
process.exit(1);
29-
}, MAX_ACTION_DURATION_MS);
30-
31-
// Make sure the timeout doesn't prevent the process from exiting naturally
16+
const actionTimeoutId = setTimeout(() => process.exit(1), MAX_ACTION_DURATION_MS);
3217
actionTimeoutId.unref();
3318

3419
async function run() {
3520
try {
36-
// Step 1: Get Witness-related inputs
3721
const witnessVersion = core.getInput("witness-version") || "0.2.11";
3822
const witnessInstallDir = core.getInput("witness-install-dir") || "./";
39-
40-
// Step 2: First download Witness binary
4123
await downloadWitness(witnessVersion, witnessInstallDir);
4224

43-
// Step 3: Now handle the GitHub Action wrapping
4425
const actionRef = core.getInput("action-ref");
4526
const downloadedActionDir = await downloadAndExtractAction(actionRef);
4627

47-
// Step 4: Prepare witness command
4828
const step = core.getInput("step");
4929
const archivistaServer = core.getInput("archivista-server");
5030
const attestations = core.getInput("attestations").split(" ");
@@ -56,22 +36,18 @@ async function run() {
5636
const fulcioToken = core.getInput("fulcio-token");
5737
const intermediates = core.getInput("intermediates").split(" ");
5838
const key = core.getInput("key");
59-
let outfile = core.getInput("outfile");
60-
outfile = outfile ? outfile : path.join(os.tmpdir(), step + "-attestation.json");
39+
let outfile = core.getInput("outfile") || path.join(os.tmpdir(), step + "-attestation.json");
6140
const productExcludeGlob = core.getInput("product-exclude-glob");
6241
const productIncludeGlob = core.getInput("product-include-glob");
6342
const spiffeSocket = core.getInput("spiffe-socket");
64-
6543
let timestampServers = core.getInput("timestamp-servers");
6644
const trace = core.getInput("trace");
6745
const enableSigstore = core.getInput("enable-sigstore") === "true";
68-
6946
const exportLink = core.getInput("attestor-link-export") === "true";
7047
const exportSBOM = core.getInput("attestor-sbom-export") === "true";
7148
const exportSLSA = core.getInput("attestor-slsa-export") === "true";
7249
const mavenPOM = core.getInput("attestor-maven-pom-path");
7350

74-
// Step 5: Run the downloaded action with Witness
7551
const witnessOutput = await runActionWithWitness(
7652
downloadedActionDir,
7753
{
@@ -100,72 +76,45 @@ async function run() {
10076
}
10177
);
10278

103-
// Step 6: Process the output
10479
const gitOIDs = extractDesiredGitOIDs(witnessOutput);
105-
10680
for (const gitOID of gitOIDs) {
107-
console.log("Extracted GitOID:", gitOID);
10881
core.setOutput("git_oid", gitOID);
109-
const artifactURL = `${archivistaServer}/download/${gitOID}`;
110-
const summaryHeader = `
82+
if (process.env.GITHUB_STEP_SUMMARY) {
83+
const summaryHeader = `
11184
## Attestations Created
11285
| Step | Attestors Run | Attestation GitOID
11386
| --- | --- | --- |
11487
`;
115-
try {
116-
if (process.env.GITHUB_STEP_SUMMARY) {
117-
const summaryFile = fs.readFileSync(process.env.GITHUB_STEP_SUMMARY, { encoding: "utf-8" });
118-
const headerExists = summaryFile.includes(summaryHeader.trim());
119-
if (!headerExists) {
120-
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, summaryHeader);
121-
}
122-
const tableRow = `| ${step} | ${attestations.join(", ")} | [${gitOID}](${artifactURL}) |\n`;
123-
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, tableRow);
88+
let summaryFile = fs.readFileSync(process.env.GITHUB_STEP_SUMMARY, "utf-8");
89+
if (!summaryFile.includes(summaryHeader.trim())) {
90+
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, summaryHeader);
12491
}
125-
} catch (error) {
126-
core.warning(`Could not write to GitHub step summary: ${error.message}`);
92+
const artifactURL = `${archivistaServer}/download/${gitOID}`;
93+
const tableRow = `| ${step} | ${attestations.join(", ")} | [${gitOID}](${artifactURL}) |\n`;
94+
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, tableRow);
12795
}
12896
}
12997
} catch (error) {
13098
core.setFailed(`Wrapper action failed: ${error.message}`);
131-
if (error.response) {
132-
core.error(`HTTP status: ${error.response.status}`);
133-
}
13499
}
135100
}
136101

137102
async function downloadWitness(version, installDir) {
138103
let witnessPath = tc.find("witness", version);
139-
console.log("Cached Witness Path: " + witnessPath);
140104
if (!witnessPath) {
141-
console.log("Witness not found in cache, downloading now");
142105
let witnessTar;
143106
if (process.platform === "win32") {
144-
witnessTar = await tc.downloadTool(
145-
"https://github.com/in-toto/witness/releases/download/v" + version + "/witness_" + version + "_windows_amd64.tar.gz"
146-
);
107+
witnessTar = await tc.downloadTool(`https://github.com/in-toto/witness/releases/download/v${version}/witness_${version}_windows_amd64.tar.gz`);
147108
} else if (process.platform === "darwin") {
148-
witnessTar = await tc.downloadTool(
149-
"https://github.com/in-toto/witness/releases/download/v" + version + "/witness_" + version + "_darwin_amd64.tar.gz"
150-
);
109+
witnessTar = await tc.downloadTool(`https://github.com/in-toto/witness/releases/download/v${version}/witness_${version}_darwin_amd64.tar.gz`);
151110
} else {
152-
witnessTar = await tc.downloadTool(
153-
"https://github.com/in-toto/witness/releases/download/v" + version + "/witness_" + version + "_linux_amd64.tar.gz"
154-
);
111+
witnessTar = await tc.downloadTool(`https://github.com/in-toto/witness/releases/download/v${version}/witness_${version}_linux_amd64.tar.gz`);
155112
}
156113
if (!fs.existsSync(installDir)) {
157-
console.log("Creating witness install directory at " + installDir);
158114
fs.mkdirSync(installDir, { recursive: true });
159115
}
160-
console.log("Extracting witness at: " + installDir);
161116
witnessPath = await tc.extractTar(witnessTar, installDir);
162-
const cachedPath = await tc.cacheFile(
163-
path.join(witnessPath, "witness"),
164-
"witness",
165-
"witness",
166-
version
167-
);
168-
console.log("Witness cached at: " + cachedPath);
117+
const cachedPath = await tc.cacheFile(path.join(witnessPath, "witness"), "witness", "witness", version);
169118
witnessPath = cachedPath;
170119
}
171120
core.addPath(witnessPath);
@@ -174,64 +123,46 @@ async function downloadWitness(version, installDir) {
174123

175124
async function downloadAndExtractAction(actionRef) {
176125
const [repo, ref] = parseActionRef(actionRef);
177-
core.info(`Parsed repo: ${repo}, ref: ${ref}`);
178126
const isTag = !ref.includes('/');
179127
const zipUrl = isTag
180128
? `https://github.com/${repo}/archive/refs/tags/${ref}.zip`
181129
: `https://github.com/${repo}/archive/refs/heads/${ref}.zip`;
182-
core.info(`Downloading action from: ${zipUrl}`);
183130
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "nested-action-"));
184131
try {
185132
const response = await axios({
186133
url: zipUrl,
187134
method: "GET",
188135
responseType: "stream",
189-
validateStatus: function (status) { return status >= 200 && status < 300; },
136+
validateStatus: status => status >= 200 && status < 300,
190137
maxRedirects: 5
191138
});
192139
await new Promise((resolve, reject) => {
193-
response.data.pipe(unzipper.Extract({ path: tempDir }))
194-
.on("close", resolve)
195-
.on("error", reject);
140+
response.data.pipe(unzipper.Extract({ path: tempDir })).on("close", resolve).on("error", reject);
196141
});
197-
core.info(`Downloaded and extracted to ${tempDir}`);
198142
} catch (error) {
199-
if (error.response) {
200-
core.error(`Download failed with status ${error.response.status}`);
201-
if (isTag) {
202-
core.info("Attempting alternative download URL for branches...");
203-
const altZipUrl = `https://github.com/${repo}/archive/refs/heads/${ref}.zip`;
204-
core.info(`Trying alternative URL: ${altZipUrl}`);
205-
const altResponse = await axios({
206-
url: altZipUrl,
207-
method: "GET",
208-
responseType: "stream",
209-
maxRedirects: 5
210-
});
211-
await new Promise((resolve, reject) => {
212-
altResponse.data.pipe(unzipper.Extract({ path: tempDir }))
213-
.on("close", resolve)
214-
.on("error", reject);
215-
});
216-
core.info(`Downloaded and extracted from alternative URL to ${tempDir}`);
217-
} else {
218-
throw error;
219-
}
143+
if (error.response && isTag) {
144+
const altZipUrl = `https://github.com/${repo}/archive/refs/heads/${ref}.zip`;
145+
const altResponse = await axios({
146+
url: altZipUrl,
147+
method: "GET",
148+
responseType: "stream",
149+
maxRedirects: 5
150+
});
151+
await new Promise((resolve, reject) => {
152+
altResponse.data.pipe(unzipper.Extract({ path: tempDir })).on("close", resolve).on("error", reject);
153+
});
220154
} else {
221155
throw error;
222156
}
223157
}
224-
core.debug(`Temporary directory contents: ${fs.readdirSync(tempDir).join(', ')}`);
225158
const repoName = repo.split("/")[1];
226159
const extractedFolder = path.join(tempDir, `${repoName}-${ref}`);
227160
if (!fs.existsSync(extractedFolder)) {
228161
const tempContents = fs.readdirSync(tempDir);
229162
if (tempContents.length === 1 && fs.lstatSync(path.join(tempDir, tempContents[0])).isDirectory()) {
230-
const alternateFolder = path.join(tempDir, tempContents[0]);
231-
core.info(`Using alternative extracted folder: ${alternateFolder}`);
232-
return alternateFolder;
163+
return path.join(tempDir, tempContents[0]);
233164
} else {
234-
throw new Error(`Extracted folder ${extractedFolder} not found and could not determine alternative.`);
165+
throw new Error(`Extracted folder ${extractedFolder} not found.`);
235166
}
236167
}
237168
return extractedFolder;
@@ -273,30 +204,21 @@ async function runActionWithWitness(actionDir, witnessOptions) {
273204
} else {
274205
throw new Error(`Neither action.yml nor action.yaml found in ${actionDir}`);
275206
}
276-
277207
const entryPoint = actionConfig.runs && actionConfig.runs.main;
278208
if (!entryPoint) {
279209
throw new Error("Entry point (runs.main) not defined in action metadata");
280210
}
281-
core.info(`Nested action entry point: ${entryPoint}`);
282-
283211
const entryFile = path.join(actionDir, entryPoint);
284212
if (!fs.existsSync(entryFile)) {
285213
throw new Error(`Entry file ${entryFile} does not exist.`);
286214
}
287-
288215
const pkgJsonPath = path.join(actionDir, "package.json");
289216
if (fs.existsSync(pkgJsonPath)) {
290-
core.info("Installing dependencies for nested action...");
291217
await exec.exec("npm", ["install"], { cwd: actionDir });
292218
}
293-
294219
const envVars = { ...process.env };
295-
// For testing, force the nested action to see a value for INPUT_WHO_TO_GREET
296220
envVars["INPUT_WHO_TO_GREET"] = envVars["INPUT_WHO_TO_GREET"] || "Sigstore";
297-
core.info(`For testing, setting INPUT_WHO_TO_GREET to: ${envVars["INPUT_WHO_TO_GREET"]}`);
298221

299-
// Build the witness run command
300222
const cmd = ["run"];
301223
if (enableSigstore) {
302224
fulcio = fulcio || "https://fulcio.sigstore.dev";
@@ -305,9 +227,9 @@ async function runActionWithWitness(actionDir, witnessOptions) {
305227
timestampServers = "https://freetsa.org/tsr " + timestampServers;
306228
}
307229
if (attestations.length) {
308-
attestations.forEach((attestation) => {
230+
attestations.forEach(attestation => {
309231
attestation = attestation.trim();
310-
if (attestation.length > 0) {
232+
if (attestation) {
311233
cmd.push(`-a=${attestation}`);
312234
}
313235
});
@@ -324,9 +246,9 @@ async function runActionWithWitness(actionDir, witnessOptions) {
324246
if (fulcioOidcIssuer) cmd.push(`--signer-fulcio-oidc-issuer=${fulcioOidcIssuer}`);
325247
if (fulcioToken) cmd.push(`--signer-fulcio-token=${fulcioToken}`);
326248
if (intermediates.length) {
327-
intermediates.forEach((intermediate) => {
249+
intermediates.forEach(intermediate => {
328250
intermediate = intermediate.trim();
329-
if (intermediate.length > 0) {
251+
if (intermediate) {
330252
cmd.push(`-i=${intermediate}`);
331253
}
332254
});
@@ -337,70 +259,29 @@ async function runActionWithWitness(actionDir, witnessOptions) {
337259
if (spiffeSocket) cmd.push(`--spiffe-socket=${spiffeSocket}`);
338260
if (step) cmd.push(`-s=${step}`);
339261
if (timestampServers) {
340-
const timestampServerValues = timestampServers.split(" ");
341-
timestampServerValues.forEach((timestampServer) => {
262+
timestampServers.split(" ").forEach(timestampServer => {
342263
timestampServer = timestampServer.trim();
343-
if (timestampServer.length > 0) {
264+
if (timestampServer) {
344265
cmd.push(`--timestamp-servers=${timestampServer}`);
345266
}
346267
});
347268
}
348269
if (trace) cmd.push(`--trace=${trace}`);
349270
if (outfile) cmd.push(`--outfile=${outfile}`);
350-
351-
// Instead of invoking node directly, run a shell command that exports the variable before running node
352-
const newNodeCmd = 'sh';
353-
const newNodeArgs = ['-c', `export INPUT_WHO_TO_GREET=${envVars["INPUT_WHO_TO_GREET"]} && node ${entryFile}`];
354-
const runArray = ["witness", ...cmd, "--", newNodeCmd, ...newNodeArgs];
355-
const commandString = runArray.join(" ");
356-
core.info(`Running witness command: ${commandString}`);
357-
358-
const execOptions = {
359-
cwd: actionDir,
360-
env: envVars,
361-
listeners: {
362-
stdout: (data) => { process.stdout.write(data.toString()); },
363-
stderr: (data) => { process.stderr.write(data.toString()); }
364-
}
365-
};
366-
367-
let output = '';
368-
await exec.exec('sh', ['-c', commandString], {
369-
...execOptions,
370-
listeners: {
371-
...execOptions.listeners,
372-
stdout: (data) => {
373-
const str = data.toString();
374-
output += str;
375-
process.stdout.write(str);
376-
},
377-
stderr: (data) => {
378-
const str = data.toString();
379-
output += str;
380-
process.stderr.write(str);
381-
}
382-
}
383-
});
384-
385-
return output;
271+
272+
// Run the nested action directly with node, passing envVars
273+
await exec.exec("node", [entryFile], { cwd: actionDir, env: envVars });
274+
return "";
386275
}
387276

388277
function extractDesiredGitOIDs(output) {
389-
const lines = output.split("\n");
390-
const desiredSubstring = "Stored in archivista as ";
391278
const matchArray = [];
392-
console.log("Looking for GitOID in the output");
393-
for (const line of lines) {
394-
const startIndex = line.indexOf(desiredSubstring);
395-
if (startIndex !== -1) {
396-
console.log("Checking line: ", line);
397-
const match = line.match(/[0-9a-fA-F]{64}/);
398-
if (match) {
399-
console.log("Found GitOID: ", match[0]);
400-
matchArray.push(match[0]);
401-
}
279+
output.split("\n").forEach(line => {
280+
const match = line.match(/[0-9a-fA-F]{64}/);
281+
if (match) {
282+
matchArray.push(match[0]);
402283
}
403-
}
284+
});
404285
return matchArray;
405286
}
406287

@@ -412,18 +293,6 @@ function parseActionRef(refString) {
412293
return parts;
413294
}
414295

415-
run()
416-
.then(() => {
417-
core.debug('Action wrapper completed successfully');
418-
setTimeout(() => {
419-
core.debug('Forcing process exit to prevent hanging');
420-
process.exit(0);
421-
}, 500);
422-
})
423-
.catch(error => {
424-
core.setFailed(`Action wrapper failed: ${error.message}`);
425-
setTimeout(() => {
426-
core.debug('Forcing process exit to prevent hanging');
427-
process.exit(1);
428-
}, 500);
429-
});
296+
run().catch(error => {
297+
core.setFailed(`Wrapper action failed: ${error.message}`);
298+
});

0 commit comments

Comments
 (0)