1
1
const core = require ( "@actions/core" ) ;
2
2
const exec = require ( "@actions/exec" ) ;
3
- const { exit } = require ( "process" ) ;
4
3
const process = require ( "process" ) ;
5
4
const fs = require ( "fs" ) ;
6
5
const os = require ( "os" ) ;
@@ -10,41 +9,22 @@ const unzipper = require("unzipper");
10
9
const yaml = require ( "js-yaml" ) ;
11
10
const tc = require ( "@actions/tool-cache" ) ;
12
11
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 ) ) ;
23
14
24
- // Set an absolute maximum timeout for the entire action (30 minutes)
25
15
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 ) ;
32
17
actionTimeoutId . unref ( ) ;
33
18
34
19
async function run ( ) {
35
20
try {
36
- // Step 1: Get Witness-related inputs
37
21
const witnessVersion = core . getInput ( "witness-version" ) || "0.2.11" ;
38
22
const witnessInstallDir = core . getInput ( "witness-install-dir" ) || "./" ;
39
-
40
- // Step 2: First download Witness binary
41
23
await downloadWitness ( witnessVersion , witnessInstallDir ) ;
42
24
43
- // Step 3: Now handle the GitHub Action wrapping
44
25
const actionRef = core . getInput ( "action-ref" ) ;
45
26
const downloadedActionDir = await downloadAndExtractAction ( actionRef ) ;
46
27
47
- // Step 4: Prepare witness command
48
28
const step = core . getInput ( "step" ) ;
49
29
const archivistaServer = core . getInput ( "archivista-server" ) ;
50
30
const attestations = core . getInput ( "attestations" ) . split ( " " ) ;
@@ -56,22 +36,18 @@ async function run() {
56
36
const fulcioToken = core . getInput ( "fulcio-token" ) ;
57
37
const intermediates = core . getInput ( "intermediates" ) . split ( " " ) ;
58
38
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" ) ;
61
40
const productExcludeGlob = core . getInput ( "product-exclude-glob" ) ;
62
41
const productIncludeGlob = core . getInput ( "product-include-glob" ) ;
63
42
const spiffeSocket = core . getInput ( "spiffe-socket" ) ;
64
-
65
43
let timestampServers = core . getInput ( "timestamp-servers" ) ;
66
44
const trace = core . getInput ( "trace" ) ;
67
45
const enableSigstore = core . getInput ( "enable-sigstore" ) === "true" ;
68
-
69
46
const exportLink = core . getInput ( "attestor-link-export" ) === "true" ;
70
47
const exportSBOM = core . getInput ( "attestor-sbom-export" ) === "true" ;
71
48
const exportSLSA = core . getInput ( "attestor-slsa-export" ) === "true" ;
72
49
const mavenPOM = core . getInput ( "attestor-maven-pom-path" ) ;
73
50
74
- // Step 5: Run the downloaded action with Witness
75
51
const witnessOutput = await runActionWithWitness (
76
52
downloadedActionDir ,
77
53
{
@@ -100,72 +76,45 @@ async function run() {
100
76
}
101
77
) ;
102
78
103
- // Step 6: Process the output
104
79
const gitOIDs = extractDesiredGitOIDs ( witnessOutput ) ;
105
-
106
80
for ( const gitOID of gitOIDs ) {
107
- console . log ( "Extracted GitOID:" , gitOID ) ;
108
81
core . setOutput ( "git_oid" , gitOID ) ;
109
- const artifactURL = ` ${ archivistaServer } /download/ ${ gitOID } ` ;
110
- const summaryHeader = `
82
+ if ( process . env . GITHUB_STEP_SUMMARY ) {
83
+ const summaryHeader = `
111
84
## Attestations Created
112
85
| Step | Attestors Run | Attestation GitOID
113
86
| --- | --- | --- |
114
87
` ;
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 ) ;
124
91
}
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 ) ;
127
95
}
128
96
}
129
97
} catch ( error ) {
130
98
core . setFailed ( `Wrapper action failed: ${ error . message } ` ) ;
131
- if ( error . response ) {
132
- core . error ( `HTTP status: ${ error . response . status } ` ) ;
133
- }
134
99
}
135
100
}
136
101
137
102
async function downloadWitness ( version , installDir ) {
138
103
let witnessPath = tc . find ( "witness" , version ) ;
139
- console . log ( "Cached Witness Path: " + witnessPath ) ;
140
104
if ( ! witnessPath ) {
141
- console . log ( "Witness not found in cache, downloading now" ) ;
142
105
let witnessTar ;
143
106
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` ) ;
147
108
} 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` ) ;
151
110
} 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` ) ;
155
112
}
156
113
if ( ! fs . existsSync ( installDir ) ) {
157
- console . log ( "Creating witness install directory at " + installDir ) ;
158
114
fs . mkdirSync ( installDir , { recursive : true } ) ;
159
115
}
160
- console . log ( "Extracting witness at: " + installDir ) ;
161
116
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 ) ;
169
118
witnessPath = cachedPath ;
170
119
}
171
120
core . addPath ( witnessPath ) ;
@@ -174,64 +123,46 @@ async function downloadWitness(version, installDir) {
174
123
175
124
async function downloadAndExtractAction ( actionRef ) {
176
125
const [ repo , ref ] = parseActionRef ( actionRef ) ;
177
- core . info ( `Parsed repo: ${ repo } , ref: ${ ref } ` ) ;
178
126
const isTag = ! ref . includes ( '/' ) ;
179
127
const zipUrl = isTag
180
128
? `https://github.com/${ repo } /archive/refs/tags/${ ref } .zip`
181
129
: `https://github.com/${ repo } /archive/refs/heads/${ ref } .zip` ;
182
- core . info ( `Downloading action from: ${ zipUrl } ` ) ;
183
130
const tempDir = fs . mkdtempSync ( path . join ( os . tmpdir ( ) , "nested-action-" ) ) ;
184
131
try {
185
132
const response = await axios ( {
186
133
url : zipUrl ,
187
134
method : "GET" ,
188
135
responseType : "stream" ,
189
- validateStatus : function ( status ) { return status >= 200 && status < 300 ; } ,
136
+ validateStatus : status => status >= 200 && status < 300 ,
190
137
maxRedirects : 5
191
138
} ) ;
192
139
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 ) ;
196
141
} ) ;
197
- core . info ( `Downloaded and extracted to ${ tempDir } ` ) ;
198
142
} 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
+ } ) ;
220
154
} else {
221
155
throw error ;
222
156
}
223
157
}
224
- core . debug ( `Temporary directory contents: ${ fs . readdirSync ( tempDir ) . join ( ', ' ) } ` ) ;
225
158
const repoName = repo . split ( "/" ) [ 1 ] ;
226
159
const extractedFolder = path . join ( tempDir , `${ repoName } -${ ref } ` ) ;
227
160
if ( ! fs . existsSync ( extractedFolder ) ) {
228
161
const tempContents = fs . readdirSync ( tempDir ) ;
229
162
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 ] ) ;
233
164
} else {
234
- throw new Error ( `Extracted folder ${ extractedFolder } not found and could not determine alternative .` ) ;
165
+ throw new Error ( `Extracted folder ${ extractedFolder } not found.` ) ;
235
166
}
236
167
}
237
168
return extractedFolder ;
@@ -273,30 +204,21 @@ async function runActionWithWitness(actionDir, witnessOptions) {
273
204
} else {
274
205
throw new Error ( `Neither action.yml nor action.yaml found in ${ actionDir } ` ) ;
275
206
}
276
-
277
207
const entryPoint = actionConfig . runs && actionConfig . runs . main ;
278
208
if ( ! entryPoint ) {
279
209
throw new Error ( "Entry point (runs.main) not defined in action metadata" ) ;
280
210
}
281
- core . info ( `Nested action entry point: ${ entryPoint } ` ) ;
282
-
283
211
const entryFile = path . join ( actionDir , entryPoint ) ;
284
212
if ( ! fs . existsSync ( entryFile ) ) {
285
213
throw new Error ( `Entry file ${ entryFile } does not exist.` ) ;
286
214
}
287
-
288
215
const pkgJsonPath = path . join ( actionDir , "package.json" ) ;
289
216
if ( fs . existsSync ( pkgJsonPath ) ) {
290
- core . info ( "Installing dependencies for nested action..." ) ;
291
217
await exec . exec ( "npm" , [ "install" ] , { cwd : actionDir } ) ;
292
218
}
293
-
294
219
const envVars = { ...process . env } ;
295
- // For testing, force the nested action to see a value for INPUT_WHO_TO_GREET
296
220
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" ] } ` ) ;
298
221
299
- // Build the witness run command
300
222
const cmd = [ "run" ] ;
301
223
if ( enableSigstore ) {
302
224
fulcio = fulcio || "https://fulcio.sigstore.dev" ;
@@ -305,9 +227,9 @@ async function runActionWithWitness(actionDir, witnessOptions) {
305
227
timestampServers = "https://freetsa.org/tsr " + timestampServers ;
306
228
}
307
229
if ( attestations . length ) {
308
- attestations . forEach ( ( attestation ) => {
230
+ attestations . forEach ( attestation => {
309
231
attestation = attestation . trim ( ) ;
310
- if ( attestation . length > 0 ) {
232
+ if ( attestation ) {
311
233
cmd . push ( `-a=${ attestation } ` ) ;
312
234
}
313
235
} ) ;
@@ -324,9 +246,9 @@ async function runActionWithWitness(actionDir, witnessOptions) {
324
246
if ( fulcioOidcIssuer ) cmd . push ( `--signer-fulcio-oidc-issuer=${ fulcioOidcIssuer } ` ) ;
325
247
if ( fulcioToken ) cmd . push ( `--signer-fulcio-token=${ fulcioToken } ` ) ;
326
248
if ( intermediates . length ) {
327
- intermediates . forEach ( ( intermediate ) => {
249
+ intermediates . forEach ( intermediate => {
328
250
intermediate = intermediate . trim ( ) ;
329
- if ( intermediate . length > 0 ) {
251
+ if ( intermediate ) {
330
252
cmd . push ( `-i=${ intermediate } ` ) ;
331
253
}
332
254
} ) ;
@@ -337,70 +259,29 @@ async function runActionWithWitness(actionDir, witnessOptions) {
337
259
if ( spiffeSocket ) cmd . push ( `--spiffe-socket=${ spiffeSocket } ` ) ;
338
260
if ( step ) cmd . push ( `-s=${ step } ` ) ;
339
261
if ( timestampServers ) {
340
- const timestampServerValues = timestampServers . split ( " " ) ;
341
- timestampServerValues . forEach ( ( timestampServer ) => {
262
+ timestampServers . split ( " " ) . forEach ( timestampServer => {
342
263
timestampServer = timestampServer . trim ( ) ;
343
- if ( timestampServer . length > 0 ) {
264
+ if ( timestampServer ) {
344
265
cmd . push ( `--timestamp-servers=${ timestampServer } ` ) ;
345
266
}
346
267
} ) ;
347
268
}
348
269
if ( trace ) cmd . push ( `--trace=${ trace } ` ) ;
349
270
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 "" ;
386
275
}
387
276
388
277
function extractDesiredGitOIDs ( output ) {
389
- const lines = output . split ( "\n" ) ;
390
- const desiredSubstring = "Stored in archivista as " ;
391
278
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 ] ) ;
402
283
}
403
- }
284
+ } ) ;
404
285
return matchArray ;
405
286
}
406
287
@@ -412,18 +293,6 @@ function parseActionRef(refString) {
412
293
return parts ;
413
294
}
414
295
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