@@ -24,8 +24,15 @@ package actions
24
24
// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=block
25
25
// 1.3. Continue Upload Zip Content to Blobstorage (unauthenticated request), repeat until everything is uploaded
26
26
// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=appendBlock
27
- // 1.4. Unknown xml payload to Blobstorage (unauthenticated request), ignored for now
27
+ // 1.4. BlockList xml payload to Blobstorage (unauthenticated request)
28
+ // Files of about 800MB are parallel in parallel and / or out of order, this file is needed to enshure the correct order
28
29
// PUT: http://localhost:3000/twirp/github.actions.results.api.v1.ArtifactService/UploadArtifact?sig=mO7y35r4GyjN7fwg0DTv3-Fv1NDXD84KLEgLpoPOtDI=&expires=2024-01-23+21%3A48%3A37.20833956+%2B0100+CET&artifactName=test&taskID=75&comp=blockList
30
+ // Request
31
+ // <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
32
+ // <BlockList>
33
+ // <Latest>blockId1</Latest>
34
+ // <Latest>blockId2</Latest>
35
+ // </BlockList>
29
36
// 1.5. FinalizeArtifact
30
37
// Post: /twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact
31
38
// Request
@@ -82,6 +89,7 @@ import (
82
89
"crypto/hmac"
83
90
"crypto/sha256"
84
91
"encoding/base64"
92
+ "encoding/xml"
85
93
"fmt"
86
94
"io"
87
95
"net/http"
@@ -152,31 +160,34 @@ func ArtifactsV4Routes(prefix string) *web.Route {
152
160
return m
153
161
}
154
162
155
- func (r artifactV4Routes ) buildSignature (endp , expires , artifactName string , taskID int64 ) []byte {
163
+ func (r artifactV4Routes ) buildSignature (endp , expires , artifactName string , taskID , artifactID int64 ) []byte {
156
164
mac := hmac .New (sha256 .New , setting .GetGeneralTokenSigningSecret ())
157
165
mac .Write ([]byte (endp ))
158
166
mac .Write ([]byte (expires ))
159
167
mac .Write ([]byte (artifactName ))
160
168
mac .Write ([]byte (fmt .Sprint (taskID )))
169
+ mac .Write ([]byte (fmt .Sprint (artifactID )))
161
170
return mac .Sum (nil )
162
171
}
163
172
164
- func (r artifactV4Routes ) buildArtifactURL (ctx * ArtifactContext , endp , artifactName string , taskID int64 ) string {
173
+ func (r artifactV4Routes ) buildArtifactURL (ctx * ArtifactContext , endp , artifactName string , taskID , artifactID int64 ) string {
165
174
expires := time .Now ().Add (60 * time .Minute ).Format ("2006-01-02 15:04:05.999999999 -0700 MST" )
166
175
uploadURL := strings .TrimSuffix (httplib .GuessCurrentAppURL (ctx ), "/" ) + strings .TrimSuffix (r .prefix , "/" ) +
167
- "/" + endp + "?sig=" + base64 .URLEncoding .EncodeToString (r .buildSignature (endp , expires , artifactName , taskID )) + "&expires=" + url .QueryEscape (expires ) + "&artifactName=" + url .QueryEscape (artifactName ) + "&taskID=" + fmt .Sprint (taskID )
176
+ "/" + endp + "?sig=" + base64 .URLEncoding .EncodeToString (r .buildSignature (endp , expires , artifactName , taskID , artifactID )) + "&expires=" + url .QueryEscape (expires ) + "&artifactName=" + url .QueryEscape (artifactName ) + "&taskID=" + fmt .Sprint (taskID ) + "&artifactID=" + fmt . Sprint ( artifactID )
168
177
return uploadURL
169
178
}
170
179
171
180
func (r artifactV4Routes ) verifySignature (ctx * ArtifactContext , endp string ) (* actions.ActionTask , string , bool ) {
172
181
rawTaskID := ctx .Req .URL .Query ().Get ("taskID" )
182
+ rawArtifactID := ctx .Req .URL .Query ().Get ("artifactID" )
173
183
sig := ctx .Req .URL .Query ().Get ("sig" )
174
184
expires := ctx .Req .URL .Query ().Get ("expires" )
175
185
artifactName := ctx .Req .URL .Query ().Get ("artifactName" )
176
186
dsig , _ := base64 .URLEncoding .DecodeString (sig )
177
187
taskID , _ := strconv .ParseInt (rawTaskID , 10 , 64 )
188
+ artifactID , _ := strconv .ParseInt (rawArtifactID , 10 , 64 )
178
189
179
- expecedsig := r .buildSignature (endp , expires , artifactName , taskID )
190
+ expecedsig := r .buildSignature (endp , expires , artifactName , taskID , artifactID )
180
191
if ! hmac .Equal (dsig , expecedsig ) {
181
192
log .Error ("Error unauthorized" )
182
193
ctx .Error (http .StatusUnauthorized , "Error unauthorized" )
@@ -271,6 +282,8 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
271
282
return
272
283
}
273
284
artifact .ContentEncoding = ArtifactV4ContentEncoding
285
+ artifact .FileSize = 0
286
+ artifact .FileCompressedSize = 0
274
287
if err := actions .UpdateArtifactByID (ctx , artifact .ID , artifact ); err != nil {
275
288
log .Error ("Error UpdateArtifactByID: %v" , err )
276
289
ctx .Error (http .StatusInternalServerError , "Error UpdateArtifactByID" )
@@ -279,7 +292,7 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
279
292
280
293
respData := CreateArtifactResponse {
281
294
Ok : true ,
282
- SignedUploadUrl : r .buildArtifactURL (ctx , "UploadArtifact" , artifactName , ctx .ActionTask .ID ),
295
+ SignedUploadUrl : r .buildArtifactURL (ctx , "UploadArtifact" , artifactName , ctx .ActionTask .ID , artifact . ID ),
283
296
}
284
297
r .sendProtbufBody (ctx , & respData )
285
298
}
@@ -293,38 +306,77 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) {
293
306
comp := ctx .Req .URL .Query ().Get ("comp" )
294
307
switch comp {
295
308
case "block" , "appendBlock" :
296
- // get artifact by name
297
- artifact , err := r .getArtifactByName (ctx , task .Job .RunID , artifactName )
298
- if err != nil {
299
- log .Error ("Error artifact not found: %v" , err )
300
- ctx .Error (http .StatusNotFound , "Error artifact not found" )
301
- return
309
+ blockid := ctx .Req .URL .Query ().Get ("blockid" )
310
+ if blockid == "" {
311
+ // get artifact by name
312
+ artifact , err := r .getArtifactByName (ctx , task .Job .RunID , artifactName )
313
+ if err != nil {
314
+ log .Error ("Error artifact not found: %v" , err )
315
+ ctx .Error (http .StatusNotFound , "Error artifact not found" )
316
+ return
317
+ }
318
+
319
+ _ , err = appendUploadChunk (r .fs , ctx , artifact , artifact .FileSize , ctx .Req .ContentLength , artifact .RunID )
320
+ if err != nil {
321
+ log .Error ("Error runner api getting task: task is not running" )
322
+ ctx .Error (http .StatusInternalServerError , "Error runner api getting task: task is not running" )
323
+ return
324
+ }
325
+ artifact .FileCompressedSize += ctx .Req .ContentLength
326
+ artifact .FileSize += ctx .Req .ContentLength
327
+ if err := actions .UpdateArtifactByID (ctx , artifact .ID , artifact ); err != nil {
328
+ log .Error ("Error UpdateArtifactByID: %v" , err )
329
+ ctx .Error (http .StatusInternalServerError , "Error UpdateArtifactByID" )
330
+ return
331
+ }
332
+ } else {
333
+ _ , err := r .fs .Save (fmt .Sprintf ("tmpv4%d/block-%d-%d-%s" , task .Job .RunID , task .Job .RunID , ctx .Req .ContentLength , base64 .URLEncoding .EncodeToString ([]byte (blockid ))), ctx .Req .Body , - 1 )
334
+ if err != nil {
335
+ log .Error ("Error runner api getting task: task is not running" )
336
+ ctx .Error (http .StatusInternalServerError , "Error runner api getting task: task is not running" )
337
+ return
338
+ }
302
339
}
303
-
304
- if comp == "block" {
305
- artifact .FileSize = 0
306
- artifact .FileCompressedSize = 0
307
- }
308
-
309
- _ , err = appendUploadChunk (r .fs , ctx , artifact , artifact .FileSize , ctx .Req .ContentLength , artifact .RunID )
340
+ ctx .JSON (http .StatusCreated , "appended" )
341
+ case "blocklist" :
342
+ rawArtifactID := ctx .Req .URL .Query ().Get ("artifactID" )
343
+ artifactID , _ := strconv .ParseInt (rawArtifactID , 10 , 64 )
344
+ _ , err := r .fs .Save (fmt .Sprintf ("tmpv4%d/%d-%d-blocklist" , task .Job .RunID , task .Job .RunID , artifactID ), ctx .Req .Body , - 1 )
310
345
if err != nil {
311
346
log .Error ("Error runner api getting task: task is not running" )
312
347
ctx .Error (http .StatusInternalServerError , "Error runner api getting task: task is not running" )
313
348
return
314
349
}
315
- artifact .FileCompressedSize += ctx .Req .ContentLength
316
- artifact .FileSize += ctx .Req .ContentLength
317
- if err := actions .UpdateArtifactByID (ctx , artifact .ID , artifact ); err != nil {
318
- log .Error ("Error UpdateArtifactByID: %v" , err )
319
- ctx .Error (http .StatusInternalServerError , "Error UpdateArtifactByID" )
320
- return
321
- }
322
- ctx .JSON (http .StatusCreated , "appended" )
323
- case "blocklist" :
324
350
ctx .JSON (http .StatusCreated , "created" )
325
351
}
326
352
}
327
353
354
+ type BlockList struct {
355
+ Latest []string `xml:"Latest"`
356
+ }
357
+
358
+ type Latest struct {
359
+ Value string `xml:",chardata"`
360
+ }
361
+
362
+ func (r * artifactV4Routes ) readBlockList (runID , artifactID int64 ) (* BlockList , error ) {
363
+ blockListName := fmt .Sprintf ("tmpv4%d/%d-%d-blocklist" , runID , runID , artifactID )
364
+ s , err := r .fs .Open (blockListName )
365
+ if err != nil {
366
+ return nil , err
367
+ }
368
+
369
+ xdec := xml .NewDecoder (s )
370
+ blockList := & BlockList {}
371
+ err = xdec .Decode (blockList )
372
+
373
+ delerr := r .fs .Delete (blockListName )
374
+ if delerr != nil {
375
+ log .Warn ("Failed to delete blockList %s: %v" , blockListName , delerr )
376
+ }
377
+ return blockList , err
378
+ }
379
+
328
380
func (r * artifactV4Routes ) finalizeArtifact (ctx * ArtifactContext ) {
329
381
var req FinalizeArtifactRequest
330
382
@@ -343,18 +395,34 @@ func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) {
343
395
ctx .Error (http .StatusNotFound , "Error artifact not found" )
344
396
return
345
397
}
346
- chunkMap , err := listChunksByRunID (r .fs , runID )
398
+
399
+ var chunks []* chunkFileItem
400
+ blockList , err := r .readBlockList (runID , artifact .ID )
347
401
if err != nil {
348
- log .Error ("Error merge chunks: %v" , err )
349
- ctx .Error (http .StatusInternalServerError , "Error merge chunks" )
350
- return
351
- }
352
- chunks , ok := chunkMap [artifact .ID ]
353
- if ! ok {
354
- log .Error ("Error merge chunks" )
355
- ctx .Error (http .StatusInternalServerError , "Error merge chunks" )
356
- return
402
+ log .Warn ("Failed to read BlockList, fallback to old behavior: %v" , err )
403
+ chunkMap , err := listChunksByRunID (r .fs , runID )
404
+ if err != nil {
405
+ log .Error ("Error merge chunks: %v" , err )
406
+ ctx .Error (http .StatusInternalServerError , "Error merge chunks" )
407
+ return
408
+ }
409
+ chunks , ok = chunkMap [artifact .ID ]
410
+ if ! ok {
411
+ log .Error ("Error merge chunks" )
412
+ ctx .Error (http .StatusInternalServerError , "Error merge chunks" )
413
+ return
414
+ }
415
+ } else {
416
+ chunks , err = listChunksByRunIDV4 (r .fs , runID , artifact .ID , blockList )
417
+ if err != nil {
418
+ log .Error ("Error merge chunks: %v" , err )
419
+ ctx .Error (http .StatusInternalServerError , "Error merge chunks" )
420
+ return
421
+ }
422
+ artifact .FileSize = chunks [len (chunks )- 1 ].End + 1
423
+ artifact .FileCompressedSize = chunks [len (chunks )- 1 ].End + 1
357
424
}
425
+
358
426
checksum := ""
359
427
if req .Hash != nil {
360
428
checksum = req .Hash .Value
@@ -455,7 +523,7 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
455
523
}
456
524
}
457
525
if respData .SignedUrl == "" {
458
- respData .SignedUrl = r .buildArtifactURL (ctx , "DownloadArtifact" , artifactName , ctx .ActionTask .ID )
526
+ respData .SignedUrl = r .buildArtifactURL (ctx , "DownloadArtifact" , artifactName , ctx .ActionTask .ID , artifact . ID )
459
527
}
460
528
r .sendProtbufBody (ctx , & respData )
461
529
}
0 commit comments