mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
Allows us to make use of Go features introduced in v1.26. I require a feature from v1.26 for a PR I want to make later. Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12369 Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
95 lines
2.6 KiB
Go
95 lines
2.6 KiB
Go
// Copyright 2026 The Forgejo Authors. All rights reserved.
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package actions
|
|
|
|
import (
|
|
"archive/zip"
|
|
"compress/gzip"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
|
|
actions_model "forgejo.org/models/actions"
|
|
"forgejo.org/modules/httplib"
|
|
"forgejo.org/modules/setting"
|
|
"forgejo.org/modules/storage"
|
|
"forgejo.org/services/context"
|
|
)
|
|
|
|
// ServeArtifact writes an artifact archive to the response. The given rows
|
|
// must all belong to the same logical artifact (same run_id + artifact_name)
|
|
// and be in ArtifactStatusUploadConfirmed; callers are responsible for
|
|
// validating status and repository ownership beforehand.
|
|
//
|
|
// When the artifact was produced by the v4 backend (a single zip already
|
|
// sitting in storage), it is streamed or redirected to directly. Otherwise
|
|
// (v1-v3 backend) the archive is assembled on the fly from the individual
|
|
// file rows, applying gzip decompression where needed.
|
|
func ServeArtifact(base *context.Base, artifacts []*actions_model.ActionArtifact) error {
|
|
if len(artifacts) == 0 {
|
|
return errors.New("no artifacts to serve")
|
|
}
|
|
|
|
if len(artifacts) == 1 && artifacts[0].IsV4() {
|
|
return serveV4Artifact(base, artifacts[0])
|
|
}
|
|
return serveLegacyArtifact(base, artifacts)
|
|
}
|
|
|
|
func serveV4Artifact(base *context.Base, art *actions_model.ActionArtifact) error {
|
|
if setting.Actions.ArtifactStorage.MinioConfig.ServeDirect {
|
|
u, err := storage.ActionsArtifacts.URL(art.StoragePath, art.ArtifactPath, nil)
|
|
if u != nil && err == nil {
|
|
base.Redirect(u.String())
|
|
return nil
|
|
}
|
|
}
|
|
f, err := storage.ActionsArtifacts.Open(art.StoragePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
httplib.ServeContentByReadSeeker(base.Req, base.Resp, art.ArtifactName+".zip", new(art.UpdatedUnix.AsTime()), f)
|
|
return nil
|
|
}
|
|
|
|
func serveLegacyArtifact(base *context.Base, artifacts []*actions_model.ActionArtifact) error {
|
|
name := artifacts[0].ArtifactName
|
|
base.Resp.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.zip; filename*=UTF-8''%s.zip", url.PathEscape(name), name))
|
|
|
|
writer := zip.NewWriter(base.Resp)
|
|
defer writer.Close()
|
|
|
|
for _, art := range artifacts {
|
|
if err := writeArtifactFile(writer, art); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeArtifactFile(writer *zip.Writer, art *actions_model.ActionArtifact) error {
|
|
f, err := storage.ActionsArtifacts.Open(art.StoragePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
var r io.Reader = f
|
|
if art.ContentEncoding == "gzip" {
|
|
gz, err := gzip.NewReader(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer gz.Close()
|
|
r = gz
|
|
}
|
|
|
|
w, err := writer.Create(art.ArtifactPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = io.Copy(w, r)
|
|
return err
|
|
}
|