mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-05-12 02:43:23 +02:00
Compare commits
9 Commits
dff63b3ecc
...
renovate/g
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1231a88234 | ||
|
|
1e3ab0c40a | ||
|
|
295eecb9af | ||
|
|
ef6ca957b5 | ||
|
|
8088df52b9 | ||
|
|
3ea7d39690 | ||
|
|
861d351845 | ||
|
|
cce8543d06 | ||
|
|
75643645f0 |
@@ -325,10 +325,6 @@ func (h *Handler) openDB() (*bolthold.Store, error) {
|
|||||||
func (h *Handler) find(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
func (h *Handler) find(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
|
||||||
cred := credFromContext(r.Context())
|
cred := credFromContext(r.Context())
|
||||||
keys := strings.Split(r.URL.Query().Get("keys"), ",")
|
keys := strings.Split(r.URL.Query().Get("keys"), ",")
|
||||||
// cache keys are case insensitive
|
|
||||||
for i, key := range keys {
|
|
||||||
keys[i] = strings.ToLower(key)
|
|
||||||
}
|
|
||||||
version := r.URL.Query().Get("version")
|
version := r.URL.Query().Get("version")
|
||||||
|
|
||||||
db, err := h.openDB()
|
db, err := h.openDB()
|
||||||
@@ -371,8 +367,6 @@ func (h *Handler) reserve(w http.ResponseWriter, r *http.Request, _ httprouter.P
|
|||||||
h.responseJSON(w, r, 400, err)
|
h.responseJSON(w, r, 400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// cache keys are case insensitive
|
|
||||||
api.Key = strings.ToLower(api.Key)
|
|
||||||
|
|
||||||
cache := api.ToCache()
|
cache := api.ToCache()
|
||||||
cache.Repo = cred.Repo
|
cache.Repo = cred.Repo
|
||||||
|
|||||||
@@ -459,17 +459,20 @@ func TestHandler(t *testing.T) {
|
|||||||
assert.Equal(t, contents[except], content)
|
assert.Equal(t, contents[except], content)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("case insensitive", func(t *testing.T) {
|
t.Run("case preserved", func(t *testing.T) {
|
||||||
|
// Some actions (e.g. actions/setup-go, actions/setup-node) build cache keys that contain mixed-case fragments such as RUNNER_OS=Linux,
|
||||||
|
// then compare the cacheKey returned by the cache server to their original key with case-sensitive equality to decide whether the
|
||||||
|
// cache was a complete hit. The server must therefore preserve the original key case.
|
||||||
|
|
||||||
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
|
version := "c19da02a2bd7e77277f1ac29ab45c09b7d46a4ee758284e26bb3045ad11d9d20"
|
||||||
key := strings.ToLower(t.Name())
|
key := strings.ToLower(t.Name()) + "_ABC"
|
||||||
content := make([]byte, 100)
|
content := make([]byte, 100)
|
||||||
_, err := rand.Read(content)
|
_, err := rand.Read(content)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
uploadCacheNormally(t, base, key+"_ABC", version, content)
|
uploadCacheNormally(t, base, key, version, content)
|
||||||
|
|
||||||
{
|
{
|
||||||
reqKey := key + "_aBc"
|
resp, err := testClient.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, key, version))
|
||||||
resp, err := testClient.Get(fmt.Sprintf("%s/cache?keys=%s&version=%s", base, reqKey, version))
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
require.Equal(t, 200, resp.StatusCode)
|
require.Equal(t, 200, resp.StatusCode)
|
||||||
@@ -480,7 +483,8 @@ func TestHandler(t *testing.T) {
|
|||||||
}{}
|
}{}
|
||||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
|
||||||
assert.Equal(t, "hit", got.Result)
|
assert.Equal(t, "hit", got.Result)
|
||||||
assert.Equal(t, key+"_abc", got.CacheKey)
|
assert.Equal(t, key, got.CacheKey)
|
||||||
|
assert.NotEqual(t, strings.ToLower(key), got.CacheKey)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -643,7 +647,7 @@ func uploadCacheNormally(t *testing.T, base, key, version string, content []byte
|
|||||||
}{}
|
}{}
|
||||||
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&got))
|
||||||
assert.Equal(t, "hit", got.Result)
|
assert.Equal(t, "hit", got.Result)
|
||||||
assert.Equal(t, strings.ToLower(key), got.CacheKey)
|
assert.Equal(t, key, got.CacheKey)
|
||||||
archiveLocation = got.ArchiveLocation
|
archiveLocation = got.ArchiveLocation
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,9 +38,11 @@ var (
|
|||||||
ErrNoRepo = errors.New("unable to find git repo")
|
ErrNoRepo = errors.New("unable to find git repo")
|
||||||
)
|
)
|
||||||
|
|
||||||
// acquireCloneLock returns an unlock function after locking the per-directory mutex for dir.
|
// AcquireCloneLock returns an unlock function after locking the per-directory mutex for dir.
|
||||||
// Only concurrent operations targeting the same directory are erialized; clones into different directories run in parallel.
|
// Only concurrent operations targeting the same directory are serialized; clones into different directories run in parallel.
|
||||||
func acquireCloneLock(dir string) func() {
|
// Callers reading files inside dir (e.g. tarring a checked-out action into a job container) must hold this lock too,
|
||||||
|
// otherwise a concurrent NewGitCloneExecutor on the same dir can mutate the worktree mid-read.
|
||||||
|
func AcquireCloneLock(dir string) func() {
|
||||||
v, _ := cloneLocks.LoadOrStore(dir, &sync.Mutex{})
|
v, _ := cloneLocks.LoadOrStore(dir, &sync.Mutex{})
|
||||||
mu := v.(*sync.Mutex)
|
mu := v.(*sync.Mutex)
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
@@ -305,10 +307,10 @@ func gitOptions(token string) (fetchOptions git.FetchOptions, pullOptions git.Pu
|
|||||||
func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor {
|
func NewGitCloneExecutor(input NewGitCloneExecutorInput) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
logger.Infof(" \u2601 git clone '%s' # ref=%s", input.URL, input.Ref)
|
logger.Infof("git clone '%s' # ref=%s", input.URL, input.Ref)
|
||||||
logger.Debugf(" cloning %s to %s", input.URL, input.Dir)
|
logger.Debugf(" cloning %s to %s", input.URL, input.Dir)
|
||||||
|
|
||||||
defer acquireCloneLock(input.Dir)()
|
defer AcquireCloneLock(input.Dir)()
|
||||||
|
|
||||||
refName := plumbing.ReferenceName("refs/heads/" + input.Ref)
|
refName := plumbing.ReferenceName("refs/heads/" + input.Ref)
|
||||||
r, err := CloneIfRequired(ctx, refName, input, logger)
|
r, err := CloneIfRequired(ctx, refName, input, logger)
|
||||||
|
|||||||
@@ -310,11 +310,11 @@ func TestAcquireCloneLock(t *testing.T) {
|
|||||||
t.Run("same directory serializes", func(t *testing.T) {
|
t.Run("same directory serializes", func(t *testing.T) {
|
||||||
dir := t.TempDir()
|
dir := t.TempDir()
|
||||||
|
|
||||||
unlock1 := acquireCloneLock(dir)
|
unlock1 := AcquireCloneLock(dir)
|
||||||
|
|
||||||
secondAcquired := make(chan struct{})
|
secondAcquired := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
unlock := acquireCloneLock(dir)
|
unlock := AcquireCloneLock(dir)
|
||||||
close(secondAcquired)
|
close(secondAcquired)
|
||||||
unlock()
|
unlock()
|
||||||
}()
|
}()
|
||||||
@@ -338,12 +338,12 @@ func TestAcquireCloneLock(t *testing.T) {
|
|||||||
dirA := t.TempDir()
|
dirA := t.TempDir()
|
||||||
dirB := t.TempDir()
|
dirB := t.TempDir()
|
||||||
|
|
||||||
unlockA := acquireCloneLock(dirA)
|
unlockA := AcquireCloneLock(dirA)
|
||||||
defer unlockA()
|
defer unlockA()
|
||||||
|
|
||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
unlock := acquireCloneLock(dirB)
|
unlock := AcquireCloneLock(dirB)
|
||||||
unlock()
|
unlock()
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ func NewDockerBuildExecutor(input NewDockerBuildExecutorInput) common.Executor {
|
|||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
if input.Platform != "" {
|
if input.Platform != "" {
|
||||||
logger.Infof("%sdocker build -t %s --platform %s %s", logPrefix, input.ImageTag, input.Platform, input.ContextDir)
|
logger.Infof("docker build -t %s --platform %s %s", input.ImageTag, input.Platform, input.ContextDir)
|
||||||
} else {
|
} else {
|
||||||
logger.Infof("%sdocker build -t %s %s", logPrefix, input.ImageTag, input.ContextDir)
|
logger.Infof("docker build -t %s %s", input.ImageTag, input.ContextDir)
|
||||||
}
|
}
|
||||||
if common.Dryrun(ctx) {
|
if common.Dryrun(ctx) {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ type dockerMessage struct {
|
|||||||
Progress string `json:"progress"`
|
Progress string `json:"progress"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const logPrefix = " \U0001F433 "
|
|
||||||
|
|
||||||
func logDockerResponse(logger logrus.FieldLogger, dockerResponse io.ReadCloser, isError bool) error {
|
func logDockerResponse(logger logrus.FieldLogger, dockerResponse io.ReadCloser, isError bool) error {
|
||||||
if dockerResponse == nil {
|
if dockerResponse == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor {
|
func NewDockerPullExecutor(input NewDockerPullExecutorInput) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
logger.Debugf("%sdocker pull %v", logPrefix, input.Image)
|
logger.Debugf("docker pull %v", input.Image)
|
||||||
|
|
||||||
if common.Dryrun(ctx) {
|
if common.Dryrun(ctx) {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import (
|
|||||||
"gitea.com/gitea/runner/act/common"
|
"gitea.com/gitea/runner/act/common"
|
||||||
"gitea.com/gitea/runner/act/filecollector"
|
"gitea.com/gitea/runner/act/filecollector"
|
||||||
|
|
||||||
|
"dario.cat/mergo"
|
||||||
"github.com/Masterminds/semver"
|
"github.com/Masterminds/semver"
|
||||||
"github.com/docker/cli/cli/compose/loader"
|
"github.com/docker/cli/cli/compose/loader"
|
||||||
"github.com/docker/cli/cli/connhelper"
|
"github.com/docker/cli/cli/connhelper"
|
||||||
@@ -36,7 +37,6 @@ import (
|
|||||||
"github.com/go-git/go-billy/v5/osfs"
|
"github.com/go-git/go-billy/v5/osfs"
|
||||||
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
|
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
|
||||||
"github.com/gobwas/glob"
|
"github.com/gobwas/glob"
|
||||||
"github.com/imdario/mergo"
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/kballard/go-shellquote"
|
"github.com/kballard/go-shellquote"
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
@@ -53,7 +53,7 @@ func NewContainer(input *NewContainerInput) ExecutionsEnvironment {
|
|||||||
|
|
||||||
func (cr *containerReference) ConnectToNetwork(name string) common.Executor {
|
func (cr *containerReference) ConnectToNetwork(name string) common.Executor {
|
||||||
return common.
|
return common.
|
||||||
NewDebugExecutor("%sdocker network connect %s %s", logPrefix, name, cr.input.Name).
|
NewDebugExecutor("docker network connect %s %s", name, cr.input.Name).
|
||||||
Then(
|
Then(
|
||||||
common.NewPipelineExecutor(
|
common.NewPipelineExecutor(
|
||||||
cr.connect(),
|
cr.connect(),
|
||||||
@@ -90,7 +90,7 @@ func supportsContainerImagePlatform(ctx context.Context, cli client.APIClient) b
|
|||||||
|
|
||||||
func (cr *containerReference) Create(capAdd, capDrop []string) common.Executor {
|
func (cr *containerReference) Create(capAdd, capDrop []string) common.Executor {
|
||||||
return common.
|
return common.
|
||||||
NewInfoExecutor("%sdocker create image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
|
NewInfoExecutor("docker create image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
|
||||||
Then(
|
Then(
|
||||||
common.NewPipelineExecutor(
|
common.NewPipelineExecutor(
|
||||||
cr.connect(),
|
cr.connect(),
|
||||||
@@ -102,7 +102,7 @@ func (cr *containerReference) Create(capAdd, capDrop []string) common.Executor {
|
|||||||
|
|
||||||
func (cr *containerReference) Start(attach bool) common.Executor {
|
func (cr *containerReference) Start(attach bool) common.Executor {
|
||||||
return common.
|
return common.
|
||||||
NewInfoExecutor("%sdocker run image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
|
NewInfoExecutor("docker run image=%s platform=%s entrypoint=%+q cmd=%+q network=%+q", cr.input.Image, cr.input.Platform, cr.input.Entrypoint, cr.input.Cmd, cr.input.NetworkMode).
|
||||||
Then(
|
Then(
|
||||||
common.NewPipelineExecutor(
|
common.NewPipelineExecutor(
|
||||||
cr.connect(),
|
cr.connect(),
|
||||||
@@ -125,7 +125,7 @@ func (cr *containerReference) Start(attach bool) common.Executor {
|
|||||||
|
|
||||||
func (cr *containerReference) Pull(forcePull bool) common.Executor {
|
func (cr *containerReference) Pull(forcePull bool) common.Executor {
|
||||||
return common.
|
return common.
|
||||||
NewInfoExecutor("%sdocker pull image=%s platform=%s username=%s forcePull=%t", logPrefix, cr.input.Image, cr.input.Platform, cr.input.Username, forcePull).
|
NewInfoExecutor("docker pull image=%s platform=%s username=%s forcePull=%t", cr.input.Image, cr.input.Platform, cr.input.Username, forcePull).
|
||||||
Then(
|
Then(
|
||||||
NewDockerPullExecutor(NewDockerPullExecutorInput{
|
NewDockerPullExecutor(NewDockerPullExecutorInput{
|
||||||
Image: cr.input.Image,
|
Image: cr.input.Image,
|
||||||
@@ -147,7 +147,7 @@ func (cr *containerReference) Copy(destPath string, files ...*FileEntry) common.
|
|||||||
|
|
||||||
func (cr *containerReference) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor {
|
func (cr *containerReference) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor {
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
common.NewInfoExecutor("%sdocker cp src=%s dst=%s", logPrefix, srcPath, destPath),
|
common.NewInfoExecutor("docker cp src=%s dst=%s", srcPath, destPath),
|
||||||
cr.copyDir(destPath, srcPath, useGitIgnore),
|
cr.copyDir(destPath, srcPath, useGitIgnore),
|
||||||
func(ctx context.Context) error {
|
func(ctx context.Context) error {
|
||||||
// If this fails, then folders have wrong permissions on non root container
|
// If this fails, then folders have wrong permissions on non root container
|
||||||
@@ -177,7 +177,7 @@ func (cr *containerReference) UpdateFromImageEnv(env *map[string]string) common.
|
|||||||
|
|
||||||
func (cr *containerReference) Exec(command []string, env map[string]string, user, workdir string) common.Executor {
|
func (cr *containerReference) Exec(command []string, env map[string]string, user, workdir string) common.Executor {
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
common.NewInfoExecutor("%sdocker exec cmd=[%s] user=%s workdir=%s", logPrefix, strings.Join(command, " "), user, workdir),
|
common.NewInfoExecutor("docker exec cmd=[%s] user=%s workdir=%s", strings.Join(command, " "), user, workdir),
|
||||||
cr.connect(),
|
cr.connect(),
|
||||||
cr.find(),
|
cr.find(),
|
||||||
cr.exec(command, env, user, workdir),
|
cr.exec(command, env, user, workdir),
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func NewDockerVolumeRemoveExecutor(volumeName string, force bool) common.Executo
|
|||||||
func removeExecutor(volume string, force bool) common.Executor {
|
func removeExecutor(volume string, force bool) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
logger.Debugf("%sdocker volume rm %s", logPrefix, volume)
|
logger.Debugf("docker volume rm %s", volume)
|
||||||
|
|
||||||
if common.Dryrun(ctx) {
|
if common.Dryrun(ctx) {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -73,10 +73,16 @@ func (cc *CopyCollector) WriteFile(fpath string, fi fs.FileInfo, linkName string
|
|||||||
if err := os.MkdirAll(filepath.Dir(fdestpath), 0o777); err != nil {
|
if err := os.MkdirAll(filepath.Dir(fdestpath), 0o777); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Remove any existing destination so we can overwrite read-only files
|
||||||
|
// (e.g. git pack files at mode 0444 trip EACCES on macOS and "Access is
|
||||||
|
// denied" on Windows when reopened with O_WRONLY) and so os.Symlink does
|
||||||
|
// not fail with EEXIST. os.Remove clears the Windows read-only attribute
|
||||||
|
// internally; on Unix unlink only needs write permission on the parent.
|
||||||
|
_ = os.Remove(fdestpath)
|
||||||
if f == nil {
|
if f == nil {
|
||||||
return os.Symlink(linkName, fdestpath)
|
return os.Symlink(linkName, fdestpath)
|
||||||
}
|
}
|
||||||
df, err := os.OpenFile(fdestpath, os.O_CREATE|os.O_WRONLY, fi.Mode())
|
df, err := os.OpenFile(fdestpath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, fi.Mode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import (
|
|||||||
"archive/tar"
|
"archive/tar"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -20,6 +22,7 @@ import (
|
|||||||
"github.com/go-git/go-git/v5/plumbing/format/index"
|
"github.com/go-git/go-git/v5/plumbing/format/index"
|
||||||
"github.com/go-git/go-git/v5/storage/filesystem"
|
"github.com/go-git/go-git/v5/storage/filesystem"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type memoryFs struct {
|
type memoryFs struct {
|
||||||
@@ -174,3 +177,47 @@ func TestSymlinks(t *testing.T) {
|
|||||||
assert.Equal(t, ".env", files["test.env"].Linkname)
|
assert.Equal(t, ".env", files["test.env"].Linkname)
|
||||||
assert.ErrorIs(t, err, io.EOF, "tar must be read cleanly to EOF")
|
assert.ErrorIs(t, err, io.EOF, "tar must be read cleanly to EOF")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression for https://gitea.com/gitea/runner/issues/876 and /941:
|
||||||
|
// re-copying an action directory must overwrite a pre-existing read-only
|
||||||
|
// file (e.g. a git pack .idx at mode 0444) instead of failing with EACCES
|
||||||
|
// on macOS or "Access is denied" on Windows.
|
||||||
|
func TestCopyCollectorWriteFileOverwritesReadOnlyFile(t *testing.T) {
|
||||||
|
dst := t.TempDir()
|
||||||
|
target := filepath.Join(dst, "sub", "pack.idx")
|
||||||
|
require.NoError(t, os.MkdirAll(filepath.Dir(target), 0o755))
|
||||||
|
require.NoError(t, os.WriteFile(target, []byte("old"), 0o444))
|
||||||
|
|
||||||
|
src := filepath.Join(t.TempDir(), "pack.idx")
|
||||||
|
require.NoError(t, os.WriteFile(src, []byte("new"), 0o444))
|
||||||
|
fi, err := os.Stat(src)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cc := &CopyCollector{DstDir: dst}
|
||||||
|
require.NoError(t, cc.WriteFile("sub/pack.idx", fi, "", strings.NewReader("new")))
|
||||||
|
|
||||||
|
got, err := os.ReadFile(target)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "new", string(got))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without the destination removal, os.Symlink fails with EEXIST when the
|
||||||
|
// path already holds a regular file from an earlier copy of the action.
|
||||||
|
func TestCopyCollectorWriteFileOverwritesFileWithSymlink(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("creating symlinks requires elevated privileges on Windows")
|
||||||
|
}
|
||||||
|
dst := t.TempDir()
|
||||||
|
target := filepath.Join(dst, "link")
|
||||||
|
require.NoError(t, os.WriteFile(target, []byte("stale"), 0o644))
|
||||||
|
|
||||||
|
fi, err := os.Lstat(target)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cc := &CopyCollector{DstDir: dst}
|
||||||
|
require.NoError(t, cc.WriteFile("link", fi, "target", nil))
|
||||||
|
|
||||||
|
resolved, err := os.Readlink(target)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "target", resolved)
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitea.com/gitea/runner/act/common"
|
"gitea.com/gitea/runner/act/common"
|
||||||
|
"gitea.com/gitea/runner/act/common/git"
|
||||||
"gitea.com/gitea/runner/act/container"
|
"gitea.com/gitea/runner/act/container"
|
||||||
"gitea.com/gitea/runner/act/model"
|
"gitea.com/gitea/runner/act/model"
|
||||||
|
|
||||||
@@ -44,6 +45,11 @@ type runAction func(step actionStep, actionDir string, remoteAction *remoteActio
|
|||||||
//go:embed res/trampoline.js
|
//go:embed res/trampoline.js
|
||||||
var trampoline embed.FS
|
var trampoline embed.FS
|
||||||
|
|
||||||
|
var (
|
||||||
|
ContainerImageExistsLocally = container.ImageExistsLocally
|
||||||
|
ContainerNewDockerBuildExecutor = container.NewDockerBuildExecutor
|
||||||
|
)
|
||||||
|
|
||||||
func readActionImpl(ctx context.Context, step *model.Step, actionDir, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) {
|
func readActionImpl(ctx context.Context, step *model.Step, actionDir, actionPath string, readFile actionYamlReader, writeFile fileWriter) (*model.Action, error) {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
allErrors := []error{}
|
allErrors := []error{}
|
||||||
@@ -148,6 +154,8 @@ func maybeCopyToActionDir(ctx context.Context, step actionStep, actionDir, actio
|
|||||||
return rc.JobContainer.CopyTarStream(ctx, containerActionDirCopy, ta)
|
return rc.JobContainer.CopyTarStream(ctx, containerActionDirCopy, ta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer git.AcquireCloneLock(actionDir)()
|
||||||
|
|
||||||
if err := removeGitIgnore(ctx, actionDir); err != nil {
|
if err := removeGitIgnore(ctx, actionDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -197,7 +205,7 @@ func runActionImpl(step actionStep, actionDir string, remoteAction *remoteAction
|
|||||||
if remoteAction == nil {
|
if remoteAction == nil {
|
||||||
location = containerActionDir
|
location = containerActionDir
|
||||||
}
|
}
|
||||||
return execAsDocker(ctx, step, actionName, location, remoteAction == nil)
|
return execAsDocker(ctx, step, actionName, actionDir, location, remoteAction == nil)
|
||||||
case x.IsComposite():
|
case x.IsComposite():
|
||||||
if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil {
|
if err := maybeCopyToActionDir(ctx, step, actionDir, actionPath, containerActionDir); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -265,7 +273,7 @@ func removeGitIgnore(ctx context.Context, directory string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: break out parts of function to reduce complexicity
|
// TODO: break out parts of function to reduce complexicity
|
||||||
func execAsDocker(ctx context.Context, step actionStep, actionName, basedir string, localAction bool) error {
|
func execAsDocker(ctx context.Context, step actionStep, actionName, actionDir, basedir string, localAction bool) error {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
rc := step.getRunContext()
|
rc := step.getRunContext()
|
||||||
action := step.getActionModel()
|
action := step.getActionModel()
|
||||||
@@ -284,12 +292,12 @@ func execAsDocker(ctx context.Context, step actionStep, actionName, basedir stri
|
|||||||
image = strings.ToLower(image)
|
image = strings.ToLower(image)
|
||||||
contextDir, fileName := filepath.Split(filepath.Join(basedir, action.Runs.Image))
|
contextDir, fileName := filepath.Split(filepath.Join(basedir, action.Runs.Image))
|
||||||
|
|
||||||
anyArchExists, err := container.ImageExistsLocally(ctx, image, "any")
|
anyArchExists, err := ContainerImageExistsLocally(ctx, image, "any")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
correctArchExists, err := container.ImageExistsLocally(ctx, image, rc.Config.ContainerArchitecture)
|
correctArchExists, err := ContainerImageExistsLocally(ctx, image, rc.Config.ContainerArchitecture)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -321,13 +329,21 @@ func execAsDocker(ctx context.Context, step actionStep, actionName, basedir stri
|
|||||||
}
|
}
|
||||||
defer buildContext.Close()
|
defer buildContext.Close()
|
||||||
}
|
}
|
||||||
prepImage = container.NewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
prepImage = ContainerNewDockerBuildExecutor(container.NewDockerBuildExecutorInput{
|
||||||
ContextDir: contextDir,
|
ContextDir: contextDir,
|
||||||
Dockerfile: fileName,
|
Dockerfile: fileName,
|
||||||
ImageTag: image,
|
ImageTag: image,
|
||||||
BuildContext: buildContext,
|
BuildContext: buildContext,
|
||||||
Platform: rc.Config.ContainerArchitecture,
|
Platform: rc.Config.ContainerArchitecture,
|
||||||
})
|
})
|
||||||
|
if buildContext == nil {
|
||||||
|
// Held across the whole build: the daemon drains contextDir lazily.
|
||||||
|
inner := prepImage
|
||||||
|
prepImage = func(ctx context.Context) error {
|
||||||
|
defer git.AcquireCloneLock(actionDir)()
|
||||||
|
return inner(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.Debugf("image '%s' for architecture '%s' already exists", image, rc.Config.ContainerArchitecture)
|
logger.Debugf("image '%s' for architecture '%s' already exists", image, rc.Config.ContainerArchitecture)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,13 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.com/gitea/runner/act/common"
|
||||||
|
"gitea.com/gitea/runner/act/common/git"
|
||||||
|
"gitea.com/gitea/runner/act/container"
|
||||||
"gitea.com/gitea/runner/act/model"
|
"gitea.com/gitea/runner/act/model"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@@ -252,3 +257,153 @@ func TestActionRunner(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMaybeCopyToActionDirHoldsCloneLock(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
actionDir := t.TempDir()
|
||||||
|
|
||||||
|
releaseCopy := make(chan struct{})
|
||||||
|
release := sync.OnceFunc(func() { close(releaseCopy) })
|
||||||
|
defer release()
|
||||||
|
|
||||||
|
copyEntered := make(chan struct{})
|
||||||
|
|
||||||
|
cm := &containerMock{}
|
||||||
|
cm.On("CopyDir", "/var/run/act/actions/", actionDir+"/", false).Return(func(ctx context.Context) error {
|
||||||
|
close(copyEntered)
|
||||||
|
<-releaseCopy
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
step := &stepActionRemote{
|
||||||
|
Step: &model.Step{Uses: "remote/action@v1"},
|
||||||
|
RunContext: &RunContext{
|
||||||
|
Config: &Config{},
|
||||||
|
JobContainer: cm,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
copyDone := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
copyDone <- maybeCopyToActionDir(ctx, step, actionDir, "", "/var/run/act/actions/")
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-copyEntered:
|
||||||
|
case err := <-copyDone:
|
||||||
|
t.Fatalf("maybeCopyToActionDir returned before CopyDir was entered: %v", err)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("CopyDir was not entered within 1 second")
|
||||||
|
}
|
||||||
|
|
||||||
|
peerAcquired := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
unlock := git.AcquireCloneLock(actionDir)
|
||||||
|
close(peerAcquired)
|
||||||
|
unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-peerAcquired:
|
||||||
|
t.Fatal("peer AcquireCloneLock returned while CopyDir was running")
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
release()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-copyDone:
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("maybeCopyToActionDir returned error: %v", err)
|
||||||
|
}
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("maybeCopyToActionDir did not return after CopyDir was unblocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-peerAcquired:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("peer AcquireCloneLock did not proceed after lock released")
|
||||||
|
}
|
||||||
|
|
||||||
|
cm.AssertExpectations(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecAsDockerHoldsCloneLockForRemoteUncached(t *testing.T) {
|
||||||
|
actionDir := t.TempDir()
|
||||||
|
|
||||||
|
unlockOnce := sync.OnceFunc(git.AcquireCloneLock(actionDir))
|
||||||
|
defer unlockOnce()
|
||||||
|
|
||||||
|
innerEntered := make(chan struct{})
|
||||||
|
releaseInner := make(chan struct{})
|
||||||
|
releaseOnce := sync.OnceFunc(func() { close(releaseInner) })
|
||||||
|
defer releaseOnce()
|
||||||
|
|
||||||
|
origImageExists := ContainerImageExistsLocally
|
||||||
|
ContainerImageExistsLocally = func(_ context.Context, _, _ string) (bool, error) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
defer func() { ContainerImageExistsLocally = origImageExists }()
|
||||||
|
|
||||||
|
origBuildExec := ContainerNewDockerBuildExecutor
|
||||||
|
ContainerNewDockerBuildExecutor = func(_ container.NewDockerBuildExecutorInput) common.Executor {
|
||||||
|
return func(_ context.Context) error {
|
||||||
|
close(innerEntered)
|
||||||
|
<-releaseInner
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func() { ContainerNewDockerBuildExecutor = origBuildExec }()
|
||||||
|
|
||||||
|
step := &stepActionRemote{
|
||||||
|
Step: &model.Step{ID: "1", Uses: "remote/action@v1", With: map[string]string{}},
|
||||||
|
RunContext: &RunContext{
|
||||||
|
Config: &Config{},
|
||||||
|
Run: &model.Run{
|
||||||
|
JobID: "1",
|
||||||
|
Workflow: &model.Workflow{
|
||||||
|
Name: "wf",
|
||||||
|
Jobs: map[string]*model.Job{"1": {}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
JobContainer: &containerMock{},
|
||||||
|
},
|
||||||
|
action: &model.Action{Runs: model.ActionRuns{Using: "docker", Image: "Dockerfile"}},
|
||||||
|
env: map[string]string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() { done <- execAsDocker(ctx, step, "test-action", actionDir, actionDir, false) }()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-innerEntered:
|
||||||
|
t.Fatal("inner build executor ran before clone lock was released")
|
||||||
|
case err := <-done:
|
||||||
|
t.Fatalf("execAsDocker returned before inner was entered: %v", err)
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
unlockOnce()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-innerEntered:
|
||||||
|
case err := <-done:
|
||||||
|
t.Fatalf("execAsDocker returned without entering inner: %v", err)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("inner build executor not entered after lock released")
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel()
|
||||||
|
releaseOnce()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("execAsDocker did not return after inner was released and ctx was canceled")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func (rc *RunContext) setOutput(ctx context.Context, kvPairs map[string]string,
|
|||||||
|
|
||||||
result, ok := rc.StepResults[stepID]
|
result, ok := rc.StepResults[stepID]
|
||||||
if !ok {
|
if !ok {
|
||||||
logger.Infof(" \U00002757 no outputs used step '%s'", stepID)
|
logger.Infof("No outputs registered for step '%s'", stepID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,9 +142,16 @@ func cloneRemoteReusableWorkflow(rc *RunContext, cloneURL, ref, targetDirectory,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var modelNewWorkflowPlanner = model.NewWorkflowPlanner
|
||||||
|
|
||||||
func newReusableWorkflowExecutor(rc *RunContext, directory, workflow string) common.Executor {
|
func newReusableWorkflowExecutor(rc *RunContext, directory, workflow string) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
planner, err := model.NewWorkflowPlanner(path.Join(directory, workflow), true)
|
// Scoped to the yaml read so concurrent invocations don't serialize
|
||||||
|
// on the whole job run.
|
||||||
|
planner, err := func() (model.WorkflowPlanner, error) {
|
||||||
|
defer git.AcquireCloneLock(directory)()
|
||||||
|
return modelNewWorkflowPlanner(path.Join(directory, workflow), true)
|
||||||
|
}()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -278,7 +285,7 @@ func setReusedWorkflowCallerResult(rc *RunContext, runner Runner) common.Executo
|
|||||||
rc.caller.setReusedWorkflowJobResult(rc.JobName, reusedWorkflowJobResult)
|
rc.caller.setReusedWorkflowJobResult(rc.JobName, reusedWorkflowJobResult)
|
||||||
} else {
|
} else {
|
||||||
rc.result(reusedWorkflowJobResult)
|
rc.result(reusedWorkflowJobResult)
|
||||||
logger.WithField("jobResult", reusedWorkflowJobResult).Infof("\U0001F3C1 Job %s", reusedWorkflowJobResultMessage)
|
logger.WithField("jobResult", reusedWorkflowJobResult).Infof("Job %s", reusedWorkflowJobResultMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,15 @@ package runner
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gitea.com/gitea/runner/act/common/git"
|
||||||
"gitea.com/gitea/runner/act/model"
|
"gitea.com/gitea/runner/act/model"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -71,6 +75,54 @@ func TestReusableWorkflowCachedBranchRefRefreshes(t *testing.T) {
|
|||||||
require.Equal(t, tmpl("v2"), string(got), "cached workflow file must reflect the updated branch tip")
|
require.Equal(t, tmpl("v2"), string(got), "cached workflow file must reflect the updated branch tip")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewReusableWorkflowExecutorHoldsCloneLock(t *testing.T) {
|
||||||
|
workflowDir := t.TempDir()
|
||||||
|
|
||||||
|
unlockOnce := sync.OnceFunc(git.AcquireCloneLock(workflowDir))
|
||||||
|
defer unlockOnce()
|
||||||
|
|
||||||
|
plannerCalled := make(chan struct{})
|
||||||
|
|
||||||
|
origPlanner := modelNewWorkflowPlanner
|
||||||
|
modelNewWorkflowPlanner = func(string, bool) (model.WorkflowPlanner, error) {
|
||||||
|
close(plannerCalled)
|
||||||
|
return nil, errors.New("stop")
|
||||||
|
}
|
||||||
|
defer func() { modelNewWorkflowPlanner = origPlanner }()
|
||||||
|
|
||||||
|
rc := &RunContext{
|
||||||
|
Config: &Config{},
|
||||||
|
Run: &model.Run{Workflow: &model.Workflow{Jobs: map[string]*model.Job{}}},
|
||||||
|
}
|
||||||
|
exec := newReusableWorkflowExecutor(rc, workflowDir, "reusable.yml")
|
||||||
|
|
||||||
|
done := make(chan error, 1)
|
||||||
|
go func() { done <- exec(context.Background()) }()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-plannerCalled:
|
||||||
|
t.Fatal("planner ran while clone lock was held")
|
||||||
|
case err := <-done:
|
||||||
|
t.Fatalf("executor returned before planner was reached: %v", err)
|
||||||
|
case <-time.After(50 * time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
unlockOnce()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-plannerCalled:
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("planner not called after lock was released")
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-done:
|
||||||
|
require.Error(t, err)
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("executor did not return after planner ran")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func gitMust(t *testing.T, dir string, args ...string) {
|
func gitMust(t *testing.T, dir string, args ...string) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
cmd := exec.Command("git", args...)
|
cmd := exec.Command("git", args...)
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string) {
|
|||||||
func (rc *RunContext) startHostEnvironment() common.Executor {
|
func (rc *RunContext) startHostEnvironment() common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
rawLogger := logger.WithField("raw_output", true)
|
rawLogger := logger.WithField(rawOutputField, true)
|
||||||
logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
|
logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
|
||||||
if rc.Config.LogOutput {
|
if rc.Config.LogOutput {
|
||||||
rawLogger.Infof("%s", s)
|
rawLogger.Infof("%s", s)
|
||||||
@@ -260,11 +260,24 @@ func (rc *RunContext) startHostEnvironment() common.Executor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// printStartJobContainerGroup mirrors actions/runner's "Starting job container"
|
||||||
|
// section: emit the group header and summary, return a closer for ::endgroup::.
|
||||||
|
func printStartJobContainerGroup(ctx context.Context, image, name, network string) func() {
|
||||||
|
rawLogger := common.Logger(ctx).WithField(rawOutputField, true)
|
||||||
|
rawLogger.Infof("::group::Starting job container")
|
||||||
|
rawLogger.Infof("image: %s", image)
|
||||||
|
rawLogger.Infof("name: %s", name)
|
||||||
|
rawLogger.Infof("network: %s", network)
|
||||||
|
return func() {
|
||||||
|
rawLogger.Infof("::endgroup::")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (rc *RunContext) startJobContainer() common.Executor {
|
func (rc *RunContext) startJobContainer() common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
image := rc.platformImage(ctx)
|
image := rc.platformImage(ctx)
|
||||||
rawLogger := logger.WithField("raw_output", true)
|
rawLogger := logger.WithField(rawOutputField, true)
|
||||||
logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
|
logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
|
||||||
if rc.Config.LogOutput {
|
if rc.Config.LogOutput {
|
||||||
rawLogger.Infof("%s", s)
|
rawLogger.Infof("%s", s)
|
||||||
@@ -279,7 +292,6 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||||||
return fmt.Errorf("failed to handle credentials: %s", err)
|
return fmt.Errorf("failed to handle credentials: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Infof("\U0001f680 Start image=%s", image)
|
|
||||||
name := rc.jobContainerName()
|
name := rc.jobContainerName()
|
||||||
// For gitea, to support --volumes-from <container_name_or_id> in options.
|
// For gitea, to support --volumes-from <container_name_or_id> in options.
|
||||||
// We need to set the container name to the environment variable.
|
// We need to set the container name to the environment variable.
|
||||||
@@ -424,6 +436,7 @@ func (rc *RunContext) startJobContainer() common.Executor {
|
|||||||
return errors.New("Failed to create job container")
|
return errors.New("Failed to create job container")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer printStartJobContainerGroup(ctx, image, name, networkName)()
|
||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
rc.pullServicesImages(rc.Config.ForcePull),
|
rc.pullServicesImages(rc.Config.ForcePull),
|
||||||
rc.JobContainer.Pull(rc.Config.ForcePull),
|
rc.JobContainer.Pull(rc.Config.ForcePull),
|
||||||
@@ -753,7 +766,7 @@ func (rc *RunContext) isEnabled(ctx context.Context) (bool, error) {
|
|||||||
img := rc.platformImage(ctx)
|
img := rc.platformImage(ctx)
|
||||||
if img == "" {
|
if img == "" {
|
||||||
for _, platformName := range rc.runsOnPlatformNames(ctx) {
|
for _, platformName := range rc.runsOnPlatformNames(ctx) {
|
||||||
l.Infof("\U0001F6A7 Skipping unsupported platform -- Try running with `-P %+v=...`", platformName)
|
l.Infof("Skipping unsupported platform -- Try running with `-P %+v=...`", platformName)
|
||||||
}
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
package runner
|
package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -12,6 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"gitea.com/gitea/runner/act/common"
|
||||||
"gitea.com/gitea/runner/act/exprparser"
|
"gitea.com/gitea/runner/act/exprparser"
|
||||||
"gitea.com/gitea/runner/act/model"
|
"gitea.com/gitea/runner/act/model"
|
||||||
|
|
||||||
@@ -635,3 +637,25 @@ func TestCreateContainerNameBoundedForLongMatrixInput(t *testing.T) {
|
|||||||
assert.LessOrEqual(t, len(name+"-network"), 255)
|
assert.LessOrEqual(t, len(name+"-network"), 255)
|
||||||
assert.LessOrEqual(t, len(name+"-job1234567890"), 255)
|
assert.LessOrEqual(t, len(name+"-job1234567890"), 255)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrintStartJobContainerGroupGolden(t *testing.T) {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
logger := log.New()
|
||||||
|
logger.SetOutput(buf)
|
||||||
|
logger.SetLevel(log.InfoLevel)
|
||||||
|
logger.SetFormatter(&jobLogFormatter{color: cyan})
|
||||||
|
entry := logger.WithFields(log.Fields{"job": "j1"})
|
||||||
|
ctx := common.WithLogger(context.Background(), entry)
|
||||||
|
|
||||||
|
printStartJobContainerGroup(ctx, "node:20", "GITEA-WORKFLOW-build-JOB-test", "gitea-runner-network")()
|
||||||
|
|
||||||
|
want := strings.Join([]string{
|
||||||
|
"[j1] | ::group::Starting job container",
|
||||||
|
"[j1] | image: node:20",
|
||||||
|
"[j1] | name: GITEA-WORKFLOW-build-JOB-test",
|
||||||
|
"[j1] | network: gitea-runner-network",
|
||||||
|
"[j1] | ::endgroup::",
|
||||||
|
"",
|
||||||
|
}, "\n")
|
||||||
|
assert.Equal(t, want, buf.String())
|
||||||
|
}
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ func (sar *stepActionRemote) prepareActionExecutor() common.Executor {
|
|||||||
return common.NewPipelineExecutor(
|
return common.NewPipelineExecutor(
|
||||||
ntErr,
|
ntErr,
|
||||||
func(ctx context.Context) error {
|
func(ctx context.Context) error {
|
||||||
|
defer git.AcquireCloneLock(actionDir)()
|
||||||
actionModel, err := sar.readAction(ctx, sar.Step, actionDir, sar.remoteAction.Path, remoteReader(ctx), os.WriteFile)
|
actionModel, err := sar.readAction(ctx, sar.Step, actionDir, sar.remoteAction.Path, remoteReader(ctx), os.WriteFile)
|
||||||
sar.action = actionModel
|
sar.action = actionModel
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -40,14 +40,21 @@
|
|||||||
|
|
||||||
### Running `gitea-runner` using Docker-in-Docker (DIND)
|
### Running `gitea-runner` using Docker-in-Docker (DIND)
|
||||||
|
|
||||||
|
- `privileged` has to be set to `true` because in-container Docker daemon requires a lot of kernel capabilities and file system mounts like `procfs` and `sysfs`
|
||||||
|
- `security_opt` sets the `apparmor` profile to `rootlesskit` for hosts running AppArmor (e.g. Ubuntu, Debian), where the kernel might otherwise block user namespace changes that Docker daemon requires for startup. The `rootlesskit` profile is provided by the `docker-ce-rootless-extras` package and is present on hosts where Docker was installed via the official installer or distro packages
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
...
|
...
|
||||||
runner:
|
runner:
|
||||||
image: gitea/runner:latest-dind-rootless
|
image: gitea/runner:latest-dind-rootless
|
||||||
restart: always
|
restart: always
|
||||||
privileged: true
|
privileged: true
|
||||||
|
security_opt:
|
||||||
|
- apparmor=rootlesskit
|
||||||
depends_on:
|
depends_on:
|
||||||
- gitea
|
gitea:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: true
|
||||||
volumes:
|
volumes:
|
||||||
- ./data/runner:/data
|
- ./data/runner:/data
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
9
go.mod
9
go.mod
@@ -13,7 +13,7 @@ require (
|
|||||||
github.com/spf13/cobra v1.10.2
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
||||||
golang.org/x/term v0.42.0
|
golang.org/x/term v0.43.0
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.14.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11
|
google.golang.org/protobuf v1.36.11
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
@@ -21,6 +21,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
dario.cat/mergo v1.0.2
|
||||||
github.com/Masterminds/semver v1.5.0
|
github.com/Masterminds/semver v1.5.0
|
||||||
github.com/creack/pty v1.1.24
|
github.com/creack/pty v1.1.24
|
||||||
github.com/distribution/reference v0.6.0
|
github.com/distribution/reference v0.6.0
|
||||||
@@ -29,13 +30,12 @@ require (
|
|||||||
github.com/go-git/go-billy/v5 v5.9.0
|
github.com/go-git/go-billy/v5 v5.9.0
|
||||||
github.com/go-git/go-git/v5 v5.19.0
|
github.com/go-git/go-git/v5 v5.19.0
|
||||||
github.com/gobwas/glob v0.2.3
|
github.com/gobwas/glob v0.2.3
|
||||||
github.com/imdario/mergo v0.3.16
|
|
||||||
github.com/julienschmidt/httprouter v1.3.0
|
github.com/julienschmidt/httprouter v1.3.0
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
|
||||||
github.com/moby/buildkit v0.13.2
|
github.com/moby/buildkit v0.13.2
|
||||||
github.com/moby/patternmatcher v0.6.1
|
github.com/moby/patternmatcher v0.6.1
|
||||||
github.com/opencontainers/image-spec v1.1.1
|
github.com/opencontainers/image-spec v1.1.1
|
||||||
github.com/opencontainers/selinux v1.14.0
|
github.com/opencontainers/selinux v1.14.1
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/prometheus/client_golang v1.23.2
|
github.com/prometheus/client_golang v1.23.2
|
||||||
github.com/rhysd/actionlint v1.7.12
|
github.com/rhysd/actionlint v1.7.12
|
||||||
@@ -46,7 +46,6 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
cyphar.com/go-pathrs v0.2.3 // indirect
|
cyphar.com/go-pathrs v0.2.3 // indirect
|
||||||
dario.cat/mergo v1.0.2 // indirect
|
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||||
@@ -108,7 +107,7 @@ require (
|
|||||||
golang.org/x/crypto v0.50.0 // indirect
|
golang.org/x/crypto v0.50.0 // indirect
|
||||||
golang.org/x/net v0.53.0 // indirect
|
golang.org/x/net v0.53.0 // indirect
|
||||||
golang.org/x/sync v0.20.0 // indirect
|
golang.org/x/sync v0.20.0 // indirect
|
||||||
golang.org/x/sys v0.43.0 // indirect
|
golang.org/x/sys v0.44.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||||
google.golang.org/grpc v1.67.0 // indirect
|
google.golang.org/grpc v1.67.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
|
|||||||
61
go.sum
61
go.sum
@@ -53,10 +53,6 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
|||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/docker/cli v25.0.7+incompatible h1:scW/AbGafKmANsonsFckFHTwpz2QypoPA/zpoLnDs/E=
|
github.com/docker/cli v25.0.7+incompatible h1:scW/AbGafKmANsonsFckFHTwpz2QypoPA/zpoLnDs/E=
|
||||||
github.com/docker/cli v25.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v25.0.7+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/docker v25.0.13+incompatible h1:YeBrkUd3q0ZoRDNoEzuopwCLU+uD8GZahDHwBdsTnkU=
|
|
||||||
github.com/docker/docker v25.0.13+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/docker v25.0.14+incompatible h1:+HNue3fKbqiDHYFAriyiMjfS5u25zB0E2/R8f42lOMc=
|
|
||||||
github.com/docker/docker v25.0.14+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/docker v25.0.15+incompatible h1:JhRD6vZdk0Ms3SEMztefBISJL13NbxudQnGix6l+T5M=
|
github.com/docker/docker v25.0.15+incompatible h1:JhRD6vZdk0Ms3SEMztefBISJL13NbxudQnGix6l+T5M=
|
||||||
github.com/docker/docker v25.0.15+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v25.0.15+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
|
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
|
||||||
@@ -69,8 +65,6 @@ github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o
|
|||||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
|
||||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
|
||||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
@@ -79,14 +73,10 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
|||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0=
|
|
||||||
github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY=
|
|
||||||
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
|
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
|
||||||
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM=
|
|
||||||
github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo=
|
|
||||||
github.com/go-git/go-git/v5 v5.19.0 h1:+WkVUQZSy/F1Gb13udrMKjIM2PrzsNfDKFSfo5tkMtc=
|
github.com/go-git/go-git/v5 v5.19.0 h1:+WkVUQZSy/F1Gb13udrMKjIM2PrzsNfDKFSfo5tkMtc=
|
||||||
github.com/go-git/go-git/v5 v5.19.0/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
|
github.com/go-git/go-git/v5 v5.19.0/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
@@ -108,8 +98,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||||
github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
|
|
||||||
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
|
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
@@ -139,12 +127,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
|||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||||
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||||
github.com/mattn/go-runewidth v0.0.20 h1:WcT52H91ZUAwy8+HUkdM3THM6gXqXuLJi9O3rjcQQaQ=
|
|
||||||
github.com/mattn/go-runewidth v0.0.20/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
|
||||||
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
|
github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w=
|
||||||
github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||||
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk=
|
||||||
@@ -153,8 +137,6 @@ github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQz
|
|||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/moby/buildkit v0.13.2 h1:nXNszM4qD9E7QtG7bFWPnDI1teUQFQglBzon/IU3SzI=
|
github.com/moby/buildkit v0.13.2 h1:nXNszM4qD9E7QtG7bFWPnDI1teUQFQglBzon/IU3SzI=
|
||||||
github.com/moby/buildkit v0.13.2/go.mod h1:2cyVOv9NoHM7arphK9ZfHIWKn9YVZRFd1wXB8kKmEzY=
|
github.com/moby/buildkit v0.13.2/go.mod h1:2cyVOv9NoHM7arphK9ZfHIWKn9YVZRFd1wXB8kKmEzY=
|
||||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
|
||||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
|
||||||
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
|
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
|
||||||
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
@@ -175,12 +157,10 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
|||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||||
github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE=
|
|
||||||
github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg=
|
|
||||||
github.com/opencontainers/selinux v1.14.0 h1:k1w6YWg3w/TvfZUAc3ksdaRwGNulRbE88TxqAZxUSOE=
|
github.com/opencontainers/selinux v1.14.0 h1:k1w6YWg3w/TvfZUAc3ksdaRwGNulRbE88TxqAZxUSOE=
|
||||||
github.com/opencontainers/selinux v1.14.0/go.mod h1:LenyElirjUHszfxrjuFqC85HIeXZKumHcKMQtnaDlQQ=
|
github.com/opencontainers/selinux v1.14.0/go.mod h1:LenyElirjUHszfxrjuFqC85HIeXZKumHcKMQtnaDlQQ=
|
||||||
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
github.com/opencontainers/selinux v1.14.1 h1:a7XlXV/nN/l5zFP1FWZYoExpClu1QOPMfWUV2CZ8kEQ=
|
||||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
github.com/opencontainers/selinux v1.14.1/go.mod h1:LenyElirjUHszfxrjuFqC85HIeXZKumHcKMQtnaDlQQ=
|
||||||
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
|
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
|
||||||
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -195,8 +175,6 @@ github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9Z
|
|||||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||||
github.com/rhysd/actionlint v1.7.11 h1:m+aSuCpCIClS8X02xMG4Z8s87fCHPsAtYkAoWGQZgEE=
|
|
||||||
github.com/rhysd/actionlint v1.7.11/go.mod h1:8n50YougV9+50niD7oxgDTZ1KbN/ZnKiQ2xpLFeVhsI=
|
|
||||||
github.com/rhysd/actionlint v1.7.12 h1:vQ4GeJN86C0QH+gTUQcs8McmK62OLT3kmakPMtEWYnY=
|
github.com/rhysd/actionlint v1.7.12 h1:vQ4GeJN86C0QH+gTUQcs8McmK62OLT3kmakPMtEWYnY=
|
||||||
github.com/rhysd/actionlint v1.7.12/go.mod h1:krOUhujIsJusovkaYzQ/VNH8PFexjNKqU0q5XI/4w+g=
|
github.com/rhysd/actionlint v1.7.12/go.mod h1:krOUhujIsJusovkaYzQ/VNH8PFexjNKqU0q5XI/4w+g=
|
||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
@@ -277,14 +255,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
|||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
|
||||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
|
||||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
|
||||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
|
||||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
|
||||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
@@ -292,17 +266,11 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
|
||||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
|
||||||
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
|
|
||||||
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
|
|
||||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
|
||||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@@ -315,26 +283,17 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
||||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
|
||||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||||
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||||
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
|
|
||||||
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
|
|
||||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
|
||||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
|||||||
@@ -416,6 +416,21 @@ func (r *Reporter) Close(lastWords string) error {
|
|||||||
log.Error("No Response from RunDaemon for 60s, continue best effort")
|
log.Error("No Response from RunDaemon for 60s, continue best effort")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gitea's UpdateLog short-circuits on len(Rows)==0 before honoring NoMore,
|
||||||
|
// so a final empty request never runs TransferLogs and dbfs_data leaks.
|
||||||
|
// Inject a sentinel row after the daemon has exited so it can't be flushed
|
||||||
|
// before ReportLog(true).
|
||||||
|
// TODO: Remove after https://github.com/go-gitea/gitea/pull/37631 is in all
|
||||||
|
// supported branches, e.g. v1.28+.
|
||||||
|
r.stateMu.Lock()
|
||||||
|
if len(r.logRows) == 0 {
|
||||||
|
r.logRows = append(r.logRows, &runnerv1.LogRow{
|
||||||
|
Time: timestamppb.Now(),
|
||||||
|
Content: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
r.stateMu.Unlock()
|
||||||
|
|
||||||
// Report the job outcome even when all log upload retry attempts have been exhausted
|
// Report the job outcome even when all log upload retry attempts have been exhausted
|
||||||
return errors.Join(
|
return errors.Join(
|
||||||
retry.Do(func() error {
|
retry.Do(func() error {
|
||||||
|
|||||||
@@ -598,6 +598,68 @@ func TestReporter_StateNotifyFlush(t *testing.T) {
|
|||||||
"step transition should have triggered immediate state flush via stateNotify")
|
"step transition should have triggered immediate state flush via stateNotify")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Regression test for https://gitea.com/gitea/runner/issues/950: Close() must
|
||||||
|
// always send a final UpdateLog with NoMore=true carrying at least one row,
|
||||||
|
// otherwise the server's len(Rows)==0 short-circuit skips TransferLogs.
|
||||||
|
// TODO: Remove after https://github.com/go-gitea/gitea/pull/37631 is in all
|
||||||
|
// supported branches, e.g. v1.28+.
|
||||||
|
func TestReporter_CloseAlwaysSendsRowsWithNoMore(t *testing.T) {
|
||||||
|
var lastReq atomic.Pointer[runnerv1.UpdateLogRequest]
|
||||||
|
var noMoreCalls atomic.Int64
|
||||||
|
|
||||||
|
client := mocks.NewClient(t)
|
||||||
|
client.On("UpdateLog", mock.Anything, mock.Anything).Return(
|
||||||
|
func(_ context.Context, req *connect_go.Request[runnerv1.UpdateLogRequest]) (*connect_go.Response[runnerv1.UpdateLogResponse], error) {
|
||||||
|
lastReq.Store(req.Msg)
|
||||||
|
if req.Msg.NoMore {
|
||||||
|
noMoreCalls.Add(1)
|
||||||
|
}
|
||||||
|
return connect_go.NewResponse(&runnerv1.UpdateLogResponse{
|
||||||
|
AckIndex: req.Msg.Index + int64(len(req.Msg.Rows)),
|
||||||
|
}), nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
client.On("UpdateTask", mock.Anything, mock.Anything).Return(
|
||||||
|
func(_ context.Context, _ *connect_go.Request[runnerv1.UpdateTaskRequest]) (*connect_go.Response[runnerv1.UpdateTaskResponse], error) {
|
||||||
|
return connect_go.NewResponse(&runnerv1.UpdateTaskResponse{}), nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
taskCtx, err := structpb.NewStruct(map[string]any{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Intervals large enough that no daemon-driven flush fires during the test.
|
||||||
|
cfg, _ := config.LoadDefault("")
|
||||||
|
cfg.Runner.LogReportInterval = 10 * time.Second
|
||||||
|
cfg.Runner.LogReportMaxLatency = 10 * time.Second
|
||||||
|
cfg.Runner.StateReportInterval = 10 * time.Second
|
||||||
|
cfg.Runner.LogReportBatchSize = 1000
|
||||||
|
|
||||||
|
reporter := NewReporter(ctx, cancel, client, &runnerv1.Task{Context: taskCtx}, cfg)
|
||||||
|
reporter.ResetSteps(1)
|
||||||
|
reporter.RunDaemon()
|
||||||
|
|
||||||
|
// Simulate a successful job whose log buffer was already drained by the
|
||||||
|
// daemon (logOffset > 0, logRows empty, terminal Result set). This is the
|
||||||
|
// state Close() lands in for the typical successful job under #819.
|
||||||
|
reporter.stateMu.Lock()
|
||||||
|
reporter.logOffset = 5
|
||||||
|
reporter.state.Result = runnerv1.Result_RESULT_SUCCESS
|
||||||
|
reporter.state.Steps[0].Result = runnerv1.Result_RESULT_SUCCESS
|
||||||
|
reporter.state.StoppedAt = timestamppb.Now()
|
||||||
|
reporter.stateMu.Unlock()
|
||||||
|
|
||||||
|
require.NoError(t, reporter.Close(""))
|
||||||
|
|
||||||
|
require.Equal(t, int64(1), noMoreCalls.Load(), "Close must send exactly one UpdateLog with NoMore=true")
|
||||||
|
final := lastReq.Load()
|
||||||
|
require.NotNil(t, final)
|
||||||
|
assert.True(t, final.NoMore, "final UpdateLog must carry NoMore=true")
|
||||||
|
assert.NotEmpty(t, final.Rows, "final UpdateLog must carry at least one row")
|
||||||
|
}
|
||||||
|
|
||||||
// TestReporter_StateHeartbeat verifies that ReportState sends a heartbeat
|
// TestReporter_StateHeartbeat verifies that ReportState sends a heartbeat
|
||||||
// UpdateTask once stateReportInterval has elapsed since the last successful
|
// UpdateTask once stateReportInterval has elapsed since the last successful
|
||||||
// report, even when nothing has changed. Without this, long-running silent
|
// report, even when nothing has changed. Without this, long-running silent
|
||||||
|
|||||||
Reference in New Issue
Block a user