mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-10-29 03:16:43 +08:00
After https://gitea.com/gitea/runner/pulls/1304, logrus stayed only to forward logs from docker/cli and moby/go-archive. The runner used a small part of those: volume parsing, the build context tar, the `ssh://` connection and registry credentials. Own code that matches them replaces them, so logrus leaves the module graph. docker/cli stays for its logrus-free `opts` and `connhelper/ssh` packages. Other dependencies that the standard library or existing ones cover go too: mergo, httprouter (replaced by `http.ServeMux`), retry-go, go-isatty, go-selinux, container-device-interface, gotest.tools and go-cmp (replaced by testify). The binary shrinks by 5%. Behaviour differences: 1. An unreadable file in a build context is skipped with a warning, where go-archive left a truncated tar. 2. Build contexts no longer carry `security.capability` xattrs. 3. The cache and artifact servers answer HEAD on GET routes, and no longer redirect trailing slashes or answer OPTIONS. *Written by Claude.* --------- Co-authored-by: bircni <bircni@icloud.com> Reviewed-on: https://gitea.com/gitea/runner/pulls/1319 Reviewed-by: bircni <bircni@icloud.com> Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: silverwind <2021+silverwind@noreply.gitea.com>
112 lines
3.2 KiB
Go
112 lines
3.2 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
//go:build !(WITHOUT_DOCKER || !(linux || darwin || windows || netbsd))
|
|
|
|
package container
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/docker/cli/cli/connhelper/ssh"
|
|
)
|
|
|
|
func sshArgs(daemonURL string) ([]string, error) {
|
|
spec, err := ssh.ParseURL(daemonURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
remote := []string{"docker", "system", "dial-stdio"}
|
|
if strings.Trim(spec.Path, "/") != "" {
|
|
remote = []string{"docker", "--host=unix://" + spec.Path, "system", "dial-stdio"}
|
|
}
|
|
return spec.Command([]string{"-o", "ConnectTimeout=30", "-T"}, remote...)
|
|
}
|
|
|
|
func dialCommand(name string, args ...string) (net.Conn, error) {
|
|
conn := &commandConn{cmd: exec.Command(name, args...)}
|
|
conn.cmd.SysProcAttr = sshSysProcAttr() // a session of its own like docker's, so a ProxyCommand never reads the runner's terminal: https://github.com/docker/cli/issues/1707
|
|
conn.cmd.Stderr = &conn.stderr
|
|
conn.cmd.WaitDelay = time.Second // a ProxyCommand child keeping stderr open would otherwise block Wait after ssh exits
|
|
conn.wait = sync.OnceValue(func() error {
|
|
if err := conn.cmd.Wait(); err != nil && !errors.Is(err, exec.ErrWaitDelay) {
|
|
return fmt.Errorf("%s: %w: %s", strings.Join(conn.cmd.Args, " "), err, strings.TrimSpace(conn.stderr.String()))
|
|
}
|
|
return nil
|
|
})
|
|
var err error
|
|
if conn.stdin, err = conn.cmd.StdinPipe(); err != nil {
|
|
return nil, err
|
|
}
|
|
if conn.stdout, err = conn.cmd.StdoutPipe(); err != nil {
|
|
return nil, err
|
|
}
|
|
return conn, conn.cmd.Start()
|
|
}
|
|
|
|
type commandConn struct {
|
|
cmd *exec.Cmd
|
|
stdin io.WriteCloser
|
|
stdout io.ReadCloser
|
|
stderr cappedBuffer
|
|
wait func() error
|
|
}
|
|
|
|
func (c *commandConn) waitWithin(timeout time.Duration) error {
|
|
timer := time.AfterFunc(timeout, func() { _ = c.cmd.Process.Kill() })
|
|
defer timer.Stop()
|
|
return c.wait()
|
|
}
|
|
|
|
func (c *commandConn) Read(p []byte) (int, error) {
|
|
n, err := c.stdout.Read(p)
|
|
if errors.Is(err, io.EOF) {
|
|
if waitErr := c.waitWithin(10 * time.Second); waitErr != nil {
|
|
return n, waitErr
|
|
}
|
|
}
|
|
return n, err
|
|
}
|
|
|
|
func (c *commandConn) Write(p []byte) (int, error) { return c.stdin.Write(p) }
|
|
|
|
// CloseWrite lets the daemon see the end of a hijacked stream, like a TCP half-close.
|
|
func (c *commandConn) CloseWrite() error { return c.stdin.Close() }
|
|
|
|
func (c *commandConn) Close() error {
|
|
_ = c.stdin.Close()
|
|
if c.cmd.Process.Signal(syscall.SIGTERM) != nil { // SIGTERM lets ssh take its ProxyCommand down, Windows only kills
|
|
_ = c.cmd.Process.Kill()
|
|
}
|
|
_ = c.waitWithin(3 * time.Second)
|
|
return nil
|
|
}
|
|
|
|
func (c *commandConn) LocalAddr() net.Addr { return &net.UnixAddr{Name: "stdio", Net: "unix"} }
|
|
|
|
func (c *commandConn) RemoteAddr() net.Addr { return &net.UnixAddr{Name: "stdio", Net: "unix"} }
|
|
|
|
func (c *commandConn) SetDeadline(_deadline time.Time) error { return nil }
|
|
|
|
func (c *commandConn) SetReadDeadline(_deadline time.Time) error { return nil }
|
|
|
|
func (c *commandConn) SetWriteDeadline(_deadline time.Time) error { return nil }
|
|
|
|
type cappedBuffer struct{ bytes.Buffer }
|
|
|
|
func (b *cappedBuffer) Write(p []byte) (int, error) {
|
|
if b.Len() > 4096 {
|
|
b.Reset()
|
|
}
|
|
return b.Buffer.Write(p)
|
|
}
|