mirror of
https://gitea.com/gitea/runner.git
synced 2026-11-06 23:30:29 +00:00
121 lines
2.9 KiB
Go
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
|
|
}
|