mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-05 09:42:54 +01:00
164 lines
4.1 KiB
Go
164 lines
4.1 KiB
Go
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||
|
// SPDX-License-Identifier: MIT
|
||
|
|
||
|
package actions
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"context"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"code.gitea.io/gitea/models/dbfs"
|
||
|
"code.gitea.io/gitea/modules/log"
|
||
|
"code.gitea.io/gitea/modules/storage"
|
||
|
|
||
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
MaxLineSize = 64 * 1024
|
||
|
DBFSPrefix = "actions_log/"
|
||
|
|
||
|
timeFormat = "2006-01-02T15:04:05.0000000Z07:00"
|
||
|
defaultBufSize = MaxLineSize
|
||
|
)
|
||
|
|
||
|
func WriteLogs(ctx context.Context, filename string, offset int64, rows []*runnerv1.LogRow) ([]int, error) {
|
||
|
name := DBFSPrefix + filename
|
||
|
f, err := dbfs.OpenFile(ctx, name, os.O_WRONLY|os.O_CREATE)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("dbfs OpenFile %q: %w", name, err)
|
||
|
}
|
||
|
defer f.Close()
|
||
|
if _, err := f.Seek(offset, io.SeekStart); err != nil {
|
||
|
return nil, fmt.Errorf("dbfs Seek %q: %w", name, err)
|
||
|
}
|
||
|
|
||
|
writer := bufio.NewWriterSize(f, defaultBufSize)
|
||
|
|
||
|
ns := make([]int, 0, len(rows))
|
||
|
for _, row := range rows {
|
||
|
n, err := writer.WriteString(FormatLog(row.Time.AsTime(), row.Content) + "\n")
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
ns = append(ns, n)
|
||
|
}
|
||
|
|
||
|
if err := writer.Flush(); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return ns, nil
|
||
|
}
|
||
|
|
||
|
func ReadLogs(ctx context.Context, inStorage bool, filename string, offset, limit int64) ([]*runnerv1.LogRow, error) {
|
||
|
f, err := openLogs(ctx, inStorage, filename)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
defer f.Close()
|
||
|
|
||
|
if _, err := f.Seek(offset, io.SeekStart); err != nil {
|
||
|
return nil, fmt.Errorf("file seek: %w", err)
|
||
|
}
|
||
|
|
||
|
scanner := bufio.NewScanner(f)
|
||
|
maxLineSize := len(timeFormat) + MaxLineSize + 1
|
||
|
scanner.Buffer(make([]byte, maxLineSize), maxLineSize)
|
||
|
|
||
|
var rows []*runnerv1.LogRow
|
||
|
for scanner.Scan() && (int64(len(rows)) < limit || limit < 0) {
|
||
|
t, c, err := ParseLog(scanner.Text())
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("parse log %q: %w", scanner.Text(), err)
|
||
|
}
|
||
|
rows = append(rows, &runnerv1.LogRow{
|
||
|
Time: timestamppb.New(t),
|
||
|
Content: c,
|
||
|
})
|
||
|
}
|
||
|
|
||
|
if err := scanner.Err(); err != nil {
|
||
|
return nil, fmt.Errorf("scan: %w", err)
|
||
|
}
|
||
|
|
||
|
return rows, nil
|
||
|
}
|
||
|
|
||
|
func TransferLogs(ctx context.Context, filename string) (func(), error) {
|
||
|
name := DBFSPrefix + filename
|
||
|
remove := func() {
|
||
|
if err := dbfs.Remove(ctx, name); err != nil {
|
||
|
log.Warn("dbfs remove %q: %v", name, err)
|
||
|
}
|
||
|
}
|
||
|
f, err := dbfs.Open(ctx, name)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("dbfs open %q: %w", name, err)
|
||
|
}
|
||
|
defer f.Close()
|
||
|
|
||
|
if _, err := storage.Actions.Save(filename, f, -1); err != nil {
|
||
|
return nil, fmt.Errorf("storage save %q: %w", filename, err)
|
||
|
}
|
||
|
return remove, nil
|
||
|
}
|
||
|
|
||
|
func RemoveLogs(ctx context.Context, inStorage bool, filename string) error {
|
||
|
if !inStorage {
|
||
|
name := DBFSPrefix + filename
|
||
|
err := dbfs.Remove(ctx, name)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("dbfs remove %q: %w", name, err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
err := storage.Actions.Delete(filename)
|
||
|
if err != nil {
|
||
|
return fmt.Errorf("storage delete %q: %w", filename, err)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func openLogs(ctx context.Context, inStorage bool, filename string) (io.ReadSeekCloser, error) {
|
||
|
if !inStorage {
|
||
|
name := DBFSPrefix + filename
|
||
|
f, err := dbfs.Open(ctx, name)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("dbfs open %q: %w", name, err)
|
||
|
}
|
||
|
return f, nil
|
||
|
}
|
||
|
f, err := storage.Actions.Open(filename)
|
||
|
if err != nil {
|
||
|
return nil, fmt.Errorf("storage open %q: %w", filename, err)
|
||
|
}
|
||
|
return f, nil
|
||
|
}
|
||
|
|
||
|
func FormatLog(timestamp time.Time, content string) string {
|
||
|
// Content shouldn't contain new line, it will break log indexes, other control chars are safe.
|
||
|
content = strings.ReplaceAll(content, "\n", `\n`)
|
||
|
if len(content) > MaxLineSize {
|
||
|
content = content[:MaxLineSize]
|
||
|
}
|
||
|
return fmt.Sprintf("%s %s", timestamp.UTC().Format(timeFormat), content)
|
||
|
}
|
||
|
|
||
|
func ParseLog(in string) (time.Time, string, error) {
|
||
|
index := strings.IndexRune(in, ' ')
|
||
|
if index < 0 {
|
||
|
return time.Time{}, "", fmt.Errorf("invalid log: %q", in)
|
||
|
}
|
||
|
timestamp, err := time.Parse(timeFormat, in[:index])
|
||
|
if err != nil {
|
||
|
return time.Time{}, "", err
|
||
|
}
|
||
|
return timestamp, in[index+1:], nil
|
||
|
}
|