mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-05-15 20:24:22 +02:00
Fixes #956. Pseudo-TTY allocation is now an explicit, runner-wide opt-in via `runner.allocate_pty`, applied to both host and docker backends. Default is off, matching GitHub `actions/runner`. ```yaml runner: allocate_pty: false # default ``` **Before:** the host backend hardcoded `if true /* allocate Terminal */` and the docker backend used `term.IsTerminal(os.Stdout.Fd())`. As a result, `docker build` (and other TTY-aware tools) saw a TTY and emitted cursor-control redraw frames that flooded captured logs with thousands of duplicate-looking progress lines — only on host-mode runners in production, and on docker-mode runners when the daemon happened to be launched from a shell rather than a service. **After:** both backends consult `Config.AllocatePTY`. The `term.IsTerminal` heuristic is gone, so behavior no longer depends on whether the daemon has a controlling terminal. **Reproduction:** running `docker build` through `HostEnvironment.Exec` with output captured to a buffer: | | Before (`if true`) | After (`AllocatePTY=false`) | |---|---:|---:| | bytes captured | 18,167 | 1,048 | | ANSI CSI sequences | 556 | 0 | | cursor-up `\e[1A` | 181 | 0 | **Side fix:** `ptyWriter.AutoStop` is now `atomic.Bool`. The field is written from the exec goroutine after `cmd.Wait()` and read from the `copyPtyOutput` goroutine via `ptyWriter.Write`; existing tests never tripped the race detector because their commands produced no output before exit. The new host-mode test does. --- This PR was written with the help of Claude Opus 4.7 --------- Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Nicolas <bircni@icloud.com> Reviewed-on: https://gitea.com/gitea/runner/pulls/961 Reviewed-by: Nicolas <bircni@icloud.com> Co-authored-by: silverwind <2021+silverwind@noreply.gitea.com> Co-committed-by: silverwind <2021+silverwind@noreply.gitea.com>
146 lines
4.0 KiB
Go
146 lines
4.0 KiB
Go
// Copyright 2022 The Gitea Authors. All rights reserved.
|
|
// Copyright 2022 The nektos/act Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package runner
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"gitea.com/gitea/runner/act/common"
|
|
"gitea.com/gitea/runner/act/container"
|
|
"gitea.com/gitea/runner/act/model"
|
|
|
|
"github.com/kballard/go-shellquote"
|
|
)
|
|
|
|
type stepDocker struct {
|
|
Step *model.Step
|
|
RunContext *RunContext
|
|
env map[string]string
|
|
}
|
|
|
|
func (sd *stepDocker) pre() common.Executor {
|
|
return func(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (sd *stepDocker) main() common.Executor {
|
|
sd.env = map[string]string{}
|
|
|
|
return runStepExecutor(sd, stepStageMain, sd.runUsesContainer())
|
|
}
|
|
|
|
func (sd *stepDocker) post() common.Executor {
|
|
return func(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (sd *stepDocker) getRunContext() *RunContext {
|
|
return sd.RunContext
|
|
}
|
|
|
|
func (sd *stepDocker) getGithubContext(ctx context.Context) *model.GithubContext {
|
|
return sd.getRunContext().getGithubContext(ctx)
|
|
}
|
|
|
|
func (sd *stepDocker) getStepModel() *model.Step {
|
|
return sd.Step
|
|
}
|
|
|
|
func (sd *stepDocker) getEnv() *map[string]string {
|
|
return &sd.env
|
|
}
|
|
|
|
func (sd *stepDocker) getIfExpression(_ context.Context, _ stepStage) string {
|
|
return sd.Step.If.Value
|
|
}
|
|
|
|
func (sd *stepDocker) runUsesContainer() common.Executor {
|
|
rc := sd.RunContext
|
|
step := sd.Step
|
|
|
|
return func(ctx context.Context) error {
|
|
image := strings.TrimPrefix(step.Uses, "docker://")
|
|
eval := rc.NewExpressionEvaluator(ctx)
|
|
cmd, err := shellquote.Split(eval.Interpolate(ctx, step.With["args"]))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var entrypoint []string
|
|
if entry := eval.Interpolate(ctx, step.With["entrypoint"]); entry != "" {
|
|
entrypoint = []string{entry}
|
|
}
|
|
|
|
stepContainer := sd.newStepContainer(ctx, image, cmd, entrypoint)
|
|
|
|
return common.NewPipelineExecutor(
|
|
stepContainer.Pull(rc.Config.ForcePull),
|
|
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers),
|
|
stepContainer.Create(rc.Config.ContainerCapAdd, rc.Config.ContainerCapDrop),
|
|
stepContainer.Start(true),
|
|
).Finally(
|
|
stepContainer.Remove().IfBool(!rc.Config.ReuseContainers),
|
|
).Finally(stepContainer.Close())(ctx)
|
|
}
|
|
}
|
|
|
|
var ContainerNewContainer = container.NewContainer
|
|
|
|
func (sd *stepDocker) newStepContainer(ctx context.Context, image string, cmd, entrypoint []string) container.Container {
|
|
rc := sd.RunContext
|
|
step := sd.Step
|
|
|
|
rawLogger := common.Logger(ctx).WithField("raw_output", true)
|
|
logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
|
|
if rc.Config.LogOutput {
|
|
rawLogger.Infof("%s", s)
|
|
} else {
|
|
rawLogger.Debugf("%s", s)
|
|
}
|
|
return true
|
|
})
|
|
envList := make([]string, 0)
|
|
for k, v := range sd.env {
|
|
envList = append(envList, fmt.Sprintf("%s=%s", k, v))
|
|
}
|
|
|
|
envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TOOL_CACHE", "/opt/hostedtoolcache"))
|
|
envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_OS", "Linux"))
|
|
envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_ARCH", container.RunnerArch(ctx)))
|
|
envList = append(envList, fmt.Sprintf("%s=%s", "RUNNER_TEMP", "/tmp"))
|
|
|
|
binds, mounts := rc.GetBindsAndMounts()
|
|
networkMode := "container:" + rc.jobContainerName()
|
|
if rc.IsHostEnv(ctx) {
|
|
networkMode = "default"
|
|
}
|
|
stepContainer := ContainerNewContainer(&container.NewContainerInput{
|
|
Cmd: cmd,
|
|
Entrypoint: entrypoint,
|
|
WorkingDir: rc.JobContainer.ToContainerPath(rc.Config.Workdir),
|
|
Image: image,
|
|
Username: rc.Config.Secrets["DOCKER_USERNAME"],
|
|
Password: rc.Config.Secrets["DOCKER_PASSWORD"],
|
|
Name: createContainerName(rc.jobContainerName(), "STEP-"+step.ID),
|
|
Env: envList,
|
|
Mounts: mounts,
|
|
NetworkMode: networkMode,
|
|
Binds: binds,
|
|
Stdout: logWriter,
|
|
Stderr: logWriter,
|
|
Privileged: rc.Config.Privileged,
|
|
UsernsMode: rc.Config.UsernsMode,
|
|
Platform: rc.Config.ContainerArchitecture,
|
|
AutoRemove: rc.Config.AutoRemove,
|
|
ValidVolumes: rc.Config.ValidVolumes,
|
|
AllocatePTY: rc.Config.AllocatePTY,
|
|
})
|
|
return stepContainer
|
|
}
|