|
|
|
|
@ -24,8 +24,15 @@ package actions
@@ -24,8 +24,15 @@ package actions
|
|
|
|
|
// 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
|
|
|
|
|
// 1.3. Continue Upload Zip Content to Blobstorage (unauthenticated request), repeat until everything is uploaded
|
|
|
|
|
// 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
|
|
|
|
|
// 1.4. Unknown xml payload to Blobstorage (unauthenticated request), ignored for now
|
|
|
|
|
// 1.4. BlockList xml payload to Blobstorage (unauthenticated request)
|
|
|
|
|
// Files of about 800MB are parallel in parallel and / or out of order, this file is needed to enshure the correct order
|
|
|
|
|
// 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
|
|
|
|
|
// Request
|
|
|
|
|
// <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
|
|
|
// <BlockList>
|
|
|
|
|
// <Latest>blockId1</Latest>
|
|
|
|
|
// <Latest>blockId2</Latest>
|
|
|
|
|
// </BlockList>
|
|
|
|
|
// 1.5. FinalizeArtifact
|
|
|
|
|
// Post: /twirp/github.actions.results.api.v1.ArtifactService/FinalizeArtifact
|
|
|
|
|
// Request
|
|
|
|
|
@ -82,6 +89,7 @@ import (
@@ -82,6 +89,7 @@ import (
|
|
|
|
|
"crypto/hmac" |
|
|
|
|
"crypto/sha256" |
|
|
|
|
"encoding/base64" |
|
|
|
|
"encoding/xml" |
|
|
|
|
"fmt" |
|
|
|
|
"io" |
|
|
|
|
"net/http" |
|
|
|
|
@ -152,31 +160,34 @@ func ArtifactsV4Routes(prefix string) *web.Route {
@@ -152,31 +160,34 @@ func ArtifactsV4Routes(prefix string) *web.Route {
|
|
|
|
|
return m |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, taskID int64) []byte { |
|
|
|
|
func (r artifactV4Routes) buildSignature(endp, expires, artifactName string, taskID, artifactID int64) []byte { |
|
|
|
|
mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret()) |
|
|
|
|
mac.Write([]byte(endp)) |
|
|
|
|
mac.Write([]byte(expires)) |
|
|
|
|
mac.Write([]byte(artifactName)) |
|
|
|
|
mac.Write([]byte(fmt.Sprint(taskID))) |
|
|
|
|
mac.Write([]byte(fmt.Sprint(artifactID))) |
|
|
|
|
return mac.Sum(nil) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endp, artifactName string, taskID int64) string { |
|
|
|
|
func (r artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endp, artifactName string, taskID, artifactID int64) string { |
|
|
|
|
expires := time.Now().Add(60 * time.Minute).Format("2006-01-02 15:04:05.999999999 -0700 MST") |
|
|
|
|
uploadURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), "/") + strings.TrimSuffix(r.prefix, "/") + |
|
|
|
|
"/" + endp + "?sig=" + base64.URLEncoding.EncodeToString(r.buildSignature(endp, expires, artifactName, taskID)) + "&expires=" + url.QueryEscape(expires) + "&artifactName=" + url.QueryEscape(artifactName) + "&taskID=" + fmt.Sprint(taskID) |
|
|
|
|
"/" + 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) |
|
|
|
|
return uploadURL |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r artifactV4Routes) verifySignature(ctx *ArtifactContext, endp string) (*actions.ActionTask, string, bool) { |
|
|
|
|
rawTaskID := ctx.Req.URL.Query().Get("taskID") |
|
|
|
|
rawArtifactID := ctx.Req.URL.Query().Get("artifactID") |
|
|
|
|
sig := ctx.Req.URL.Query().Get("sig") |
|
|
|
|
expires := ctx.Req.URL.Query().Get("expires") |
|
|
|
|
artifactName := ctx.Req.URL.Query().Get("artifactName") |
|
|
|
|
dsig, _ := base64.URLEncoding.DecodeString(sig) |
|
|
|
|
taskID, _ := strconv.ParseInt(rawTaskID, 10, 64) |
|
|
|
|
artifactID, _ := strconv.ParseInt(rawArtifactID, 10, 64) |
|
|
|
|
|
|
|
|
|
expecedsig := r.buildSignature(endp, expires, artifactName, taskID) |
|
|
|
|
expecedsig := r.buildSignature(endp, expires, artifactName, taskID, artifactID) |
|
|
|
|
if !hmac.Equal(dsig, expecedsig) { |
|
|
|
|
log.Error("Error unauthorized") |
|
|
|
|
ctx.Error(http.StatusUnauthorized, "Error unauthorized") |
|
|
|
|
@ -271,6 +282,8 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
@@ -271,6 +282,8 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
|
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
artifact.ContentEncoding = ArtifactV4ContentEncoding |
|
|
|
|
artifact.FileSize = 0 |
|
|
|
|
artifact.FileCompressedSize = 0 |
|
|
|
|
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { |
|
|
|
|
log.Error("Error UpdateArtifactByID: %v", err) |
|
|
|
|
ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID") |
|
|
|
|
@ -279,7 +292,7 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
@@ -279,7 +292,7 @@ func (r *artifactV4Routes) createArtifact(ctx *ArtifactContext) {
|
|
|
|
|
|
|
|
|
|
respData := CreateArtifactResponse{ |
|
|
|
|
Ok: true, |
|
|
|
|
SignedUploadUrl: r.buildArtifactURL(ctx, "UploadArtifact", artifactName, ctx.ActionTask.ID), |
|
|
|
|
SignedUploadUrl: r.buildArtifactURL(ctx, "UploadArtifact", artifactName, ctx.ActionTask.ID, artifact.ID), |
|
|
|
|
} |
|
|
|
|
r.sendProtbufBody(ctx, &respData) |
|
|
|
|
} |
|
|
|
|
@ -293,38 +306,77 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) {
@@ -293,38 +306,77 @@ func (r *artifactV4Routes) uploadArtifact(ctx *ArtifactContext) {
|
|
|
|
|
comp := ctx.Req.URL.Query().Get("comp") |
|
|
|
|
switch comp { |
|
|
|
|
case "block", "appendBlock": |
|
|
|
|
// get artifact by name
|
|
|
|
|
artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName) |
|
|
|
|
if err != nil { |
|
|
|
|
log.Error("Error artifact not found: %v", err) |
|
|
|
|
ctx.Error(http.StatusNotFound, "Error artifact not found") |
|
|
|
|
return |
|
|
|
|
blockid := ctx.Req.URL.Query().Get("blockid") |
|
|
|
|
if blockid == "" { |
|
|
|
|
// get artifact by name
|
|
|
|
|
artifact, err := r.getArtifactByName(ctx, task.Job.RunID, artifactName) |
|
|
|
|
if err != nil { |
|
|
|
|
log.Error("Error artifact not found: %v", err) |
|
|
|
|
ctx.Error(http.StatusNotFound, "Error artifact not found") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID) |
|
|
|
|
if err != nil { |
|
|
|
|
log.Error("Error runner api getting task: task is not running") |
|
|
|
|
ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
artifact.FileCompressedSize += ctx.Req.ContentLength |
|
|
|
|
artifact.FileSize += ctx.Req.ContentLength |
|
|
|
|
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { |
|
|
|
|
log.Error("Error UpdateArtifactByID: %v", err) |
|
|
|
|
ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
_, 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) |
|
|
|
|
if err != nil { |
|
|
|
|
log.Error("Error runner api getting task: task is not running") |
|
|
|
|
ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if comp == "block" { |
|
|
|
|
artifact.FileSize = 0 |
|
|
|
|
artifact.FileCompressedSize = 0 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
_, err = appendUploadChunk(r.fs, ctx, artifact, artifact.FileSize, ctx.Req.ContentLength, artifact.RunID) |
|
|
|
|
ctx.JSON(http.StatusCreated, "appended") |
|
|
|
|
case "blocklist": |
|
|
|
|
rawArtifactID := ctx.Req.URL.Query().Get("artifactID") |
|
|
|
|
artifactID, _ := strconv.ParseInt(rawArtifactID, 10, 64) |
|
|
|
|
_, err := r.fs.Save(fmt.Sprintf("tmpv4%d/%d-%d-blocklist", task.Job.RunID, task.Job.RunID, artifactID), ctx.Req.Body, -1) |
|
|
|
|
if err != nil { |
|
|
|
|
log.Error("Error runner api getting task: task is not running") |
|
|
|
|
ctx.Error(http.StatusInternalServerError, "Error runner api getting task: task is not running") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
artifact.FileCompressedSize += ctx.Req.ContentLength |
|
|
|
|
artifact.FileSize += ctx.Req.ContentLength |
|
|
|
|
if err := actions.UpdateArtifactByID(ctx, artifact.ID, artifact); err != nil { |
|
|
|
|
log.Error("Error UpdateArtifactByID: %v", err) |
|
|
|
|
ctx.Error(http.StatusInternalServerError, "Error UpdateArtifactByID") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
ctx.JSON(http.StatusCreated, "appended") |
|
|
|
|
case "blocklist": |
|
|
|
|
ctx.JSON(http.StatusCreated, "created") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type BlockList struct { |
|
|
|
|
Latest []string `xml:"Latest"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type Latest struct { |
|
|
|
|
Value string `xml:",chardata"` |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *artifactV4Routes) readBlockList(runID, artifactID int64) (*BlockList, error) { |
|
|
|
|
blockListName := fmt.Sprintf("tmpv4%d/%d-%d-blocklist", runID, runID, artifactID) |
|
|
|
|
s, err := r.fs.Open(blockListName) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
xdec := xml.NewDecoder(s) |
|
|
|
|
blockList := &BlockList{} |
|
|
|
|
err = xdec.Decode(blockList) |
|
|
|
|
|
|
|
|
|
delerr := r.fs.Delete(blockListName) |
|
|
|
|
if delerr != nil { |
|
|
|
|
log.Warn("Failed to delete blockList %s: %v", blockListName, delerr) |
|
|
|
|
} |
|
|
|
|
return blockList, err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) { |
|
|
|
|
var req FinalizeArtifactRequest |
|
|
|
|
|
|
|
|
|
@ -343,18 +395,34 @@ func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) {
@@ -343,18 +395,34 @@ func (r *artifactV4Routes) finalizeArtifact(ctx *ArtifactContext) {
|
|
|
|
|
ctx.Error(http.StatusNotFound, "Error artifact not found") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
chunkMap, err := listChunksByRunID(r.fs, runID) |
|
|
|
|
|
|
|
|
|
var chunks []*chunkFileItem |
|
|
|
|
blockList, err := r.readBlockList(runID, artifact.ID) |
|
|
|
|
if err != nil { |
|
|
|
|
log.Error("Error merge chunks: %v", err) |
|
|
|
|
ctx.Error(http.StatusInternalServerError, "Error merge chunks") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
chunks, ok := chunkMap[artifact.ID] |
|
|
|
|
if !ok { |
|
|
|
|
log.Error("Error merge chunks") |
|
|
|
|
ctx.Error(http.StatusInternalServerError, "Error merge chunks") |
|
|
|
|
return |
|
|
|
|
log.Warn("Failed to read BlockList, fallback to old behavior: %v", err) |
|
|
|
|
chunkMap, err := listChunksByRunID(r.fs, runID) |
|
|
|
|
if err != nil { |
|
|
|
|
log.Error("Error merge chunks: %v", err) |
|
|
|
|
ctx.Error(http.StatusInternalServerError, "Error merge chunks") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
chunks, ok = chunkMap[artifact.ID] |
|
|
|
|
if !ok { |
|
|
|
|
log.Error("Error merge chunks") |
|
|
|
|
ctx.Error(http.StatusInternalServerError, "Error merge chunks") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
chunks, err = listChunksByRunIDV4(r.fs, runID, artifact.ID, blockList) |
|
|
|
|
if err != nil { |
|
|
|
|
log.Error("Error merge chunks: %v", err) |
|
|
|
|
ctx.Error(http.StatusInternalServerError, "Error merge chunks") |
|
|
|
|
return |
|
|
|
|
} |
|
|
|
|
artifact.FileSize = chunks[len(chunks)-1].End + 1 |
|
|
|
|
artifact.FileCompressedSize = chunks[len(chunks)-1].End + 1 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
checksum := "" |
|
|
|
|
if req.Hash != nil { |
|
|
|
|
checksum = req.Hash.Value |
|
|
|
|
@ -455,7 +523,7 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
@@ -455,7 +523,7 @@ func (r *artifactV4Routes) getSignedArtifactURL(ctx *ArtifactContext) {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if respData.SignedUrl == "" { |
|
|
|
|
respData.SignedUrl = r.buildArtifactURL(ctx, "DownloadArtifact", artifactName, ctx.ActionTask.ID) |
|
|
|
|
respData.SignedUrl = r.buildArtifactURL(ctx, "DownloadArtifact", artifactName, ctx.ActionTask.ID, artifact.ID) |
|
|
|
|
} |
|
|
|
|
r.sendProtbufBody(ctx, &respData) |
|
|
|
|
} |
|
|
|
|
|