11const core = require ( "@actions/core" ) ;
22const exec = require ( "@actions/exec" ) ;
3- const { exit } = require ( "process" ) ;
43const process = require ( "process" ) ;
54const fs = require ( "fs" ) ;
65const os = require ( "os" ) ;
@@ -10,41 +9,22 @@ const unzipper = require("unzipper");
109const yaml = require ( "js-yaml" ) ;
1110const 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)
2515const 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 ) ;
3217actionTimeoutId . unref ( ) ;
3318
3419async 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
137102async 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
175124async 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
388277function 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 - 9 a - f A - 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 - 9 a - f A - 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