Files
runner/internal/pkg/action/uploadartifact/content.go
T
2026-11-06 03:17:49 +00:00

121 lines
2.9 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package uploadartifact
import (
"archive/tar"
"bufio"
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"os"
"path"
"strings"
"gitea.com/gitea/runner/internal/pkg/action"
)
const fetchScript = `cd / && if command -v gzip >/dev/null 2>&1; then exec tar -czhf - -- "$@"; fi; exec tar -chf - -- "$@"`
func readFiles(ctx context.Context, c *action.Context, files []found, visit func(found) error) error {
if c.Host() {
for _, file := range files {
body, err := os.Open(file.source)
if err != nil {
return err
}
info, err := body.Stat()
if err == nil {
file.body, file.mode, file.mtime = body, info.Mode(), info.ModTime()
err = visit(file)
}
body.Close()
if err != nil {
return err
}
}
return nil
}
for len(files) > 0 {
command := []string{"sh", "-c", fetchScript, "sh"}
selected := map[string][]found{}
size, count := 0, 0
for _, file := range files {
name := "./" + strings.TrimPrefix(file.source, "/")
if size+len(name)+1 >= 32*1024 {
if count == 0 {
return fmt.Errorf("artifact path exceeds container command limit: %s", file.path)
}
break
}
count++
if _, exists := selected[path.Clean(name)]; !exists {
command = append(command, name)
size += len(name) + 1
}
selected[path.Clean(name)] = append(selected[path.Clean(name)], file)
}
output, err := c.Container.ExecOutput(ctx, command)
if err != nil {
return err
}
retry, err := readArchive(output, selected, visit)
closeErr := output.Close()
if err != nil {
return err
} else if closeErr != nil {
return closeErr
}
files = append(retry, files[count:]...)
}
return nil
}
func readArchive(output io.Reader, selected map[string][]found, visit func(found) error) ([]found, error) {
buffered := bufio.NewReader(output)
var content io.Reader = buffered
if magic, _ := buffered.Peek(2); len(magic) == 2 && magic[0] == 0x1f && magic[1] == 0x8b {
compressed, err := gzip.NewReader(buffered)
if err != nil {
return nil, err
}
defer compressed.Close()
compressed.Multistream(false) // bsdtar pads its gzip output with zeros to the block size
content = compressed
}
reader := tar.NewReader(content)
var retry []found
for {
header, err := reader.Next()
if errors.Is(err, io.EOF) {
break
} else if err != nil {
return nil, err
}
name := path.Clean(header.Name)
files := selected[name]
delete(selected, name)
if header.Typeflag == tar.TypeLink {
retry = append(retry, files...)
continue
}
if len(files) == 0 {
continue
}
retry = append(retry, files[1:]...)
file := files[0]
file.body, file.mode, file.mtime = reader, header.FileInfo().Mode(), header.ModTime
if err := visit(file); err != nil {
return nil, err
}
}
_, err := io.Copy(io.Discard, content)
if err == nil {
_, err = io.Copy(io.Discard, buffered)
}
return retry, err
}