mirror of
https://gitea.com/gitea/act_runner.git
synced 2026-03-24 15:55:03 +01:00
refactor: migrate container changes (#91)
* refactor: migrate container changes * ignore contextcheck
This commit is contained in:
@@ -3,9 +3,11 @@ package container
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/actions-oss/act-cli/pkg/common"
|
"github.com/actions-oss/act-cli/pkg/common"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewContainerInput the input for the New function
|
// NewContainerInput the input for the New function
|
||||||
@@ -82,3 +84,13 @@ const (
|
|||||||
HealthHealthy
|
HealthHealthy
|
||||||
HealthUnHealthy
|
HealthUnHealthy
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var containerAllocateTerminal bool
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
containerAllocateTerminal = term.IsTerminal(int(os.Stdout.Fd()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetContainerAllocateTerminal(val bool) {
|
||||||
|
containerAllocateTerminal = val
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"dario.cat/mergo"
|
"dario.cat/mergo"
|
||||||
"github.com/Masterminds/semver"
|
"github.com/Masterminds/semver"
|
||||||
@@ -33,7 +34,6 @@ import (
|
|||||||
"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"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"golang.org/x/term"
|
|
||||||
|
|
||||||
"github.com/actions-oss/act-cli/pkg/common"
|
"github.com/actions-oss/act-cli/pkg/common"
|
||||||
"github.com/actions-oss/act-cli/pkg/filecollector"
|
"github.com/actions-oss/act-cli/pkg/filecollector"
|
||||||
@@ -156,7 +156,7 @@ func (cr *containerReference) Exec(command []string, env map[string]string, user
|
|||||||
common.NewInfoExecutor("%sdocker exec cmd=[%s] user=%s workdir=%s", logPrefix, strings.Join(command, " "), user, workdir),
|
common.NewInfoExecutor("%sdocker exec cmd=[%s] user=%s workdir=%s", logPrefix, strings.Join(command, " "), user, workdir),
|
||||||
cr.connect(),
|
cr.connect(),
|
||||||
cr.find(),
|
cr.find(),
|
||||||
cr.exec(command, env, user, workdir),
|
cr.execExt(command, env, user, workdir),
|
||||||
).IfNot(common.Dryrun)
|
).IfNot(common.Dryrun)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -413,7 +413,7 @@ func (cr *containerReference) create(capAdd []string, capDrop []string) common.E
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
|
isTerminal := containerAllocateTerminal
|
||||||
input := cr.input
|
input := cr.input
|
||||||
|
|
||||||
config := &container.Config{
|
config := &container.Config{
|
||||||
@@ -539,75 +539,101 @@ func (cr *containerReference) extractFromImageEnv(env *map[string]string) common
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr *containerReference) exec(cmd []string, env map[string]string, user, workdir string) common.Executor {
|
func (cr *containerReference) exec(ctx context.Context, cmd []string, env map[string]string, user, workdir string) error {
|
||||||
|
logger := common.Logger(ctx)
|
||||||
|
// Fix slashes when running on Windows
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
var newCmd []string
|
||||||
|
for _, v := range cmd {
|
||||||
|
newCmd = append(newCmd, strings.ReplaceAll(v, `\`, `/`))
|
||||||
|
}
|
||||||
|
cmd = newCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Debugf("Exec command '%s'", cmd)
|
||||||
|
isTerminal := containerAllocateTerminal
|
||||||
|
envList := make([]string, 0)
|
||||||
|
for k, v := range env {
|
||||||
|
envList = append(envList, fmt.Sprintf("%s=%s", k, v))
|
||||||
|
}
|
||||||
|
|
||||||
|
var wd string
|
||||||
|
if workdir != "" {
|
||||||
|
if strings.HasPrefix(workdir, "/") {
|
||||||
|
wd = workdir
|
||||||
|
} else {
|
||||||
|
wd = fmt.Sprintf("%s/%s", cr.input.WorkingDir, workdir)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
wd = cr.input.WorkingDir
|
||||||
|
}
|
||||||
|
logger.Debugf("Working directory '%s'", wd)
|
||||||
|
|
||||||
|
idResp, err := cr.cli.ContainerExecCreate(ctx, cr.id, container.ExecOptions{
|
||||||
|
User: user,
|
||||||
|
Cmd: cmd,
|
||||||
|
WorkingDir: wd,
|
||||||
|
Env: envList,
|
||||||
|
Tty: isTerminal,
|
||||||
|
AttachStderr: true,
|
||||||
|
AttachStdout: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create exec: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := cr.cli.ContainerExecAttach(ctx, idResp.ID, container.ExecStartOptions{
|
||||||
|
Tty: isTerminal,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to attach to exec: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Close()
|
||||||
|
|
||||||
|
err = cr.waitForCommand(ctx, isTerminal, resp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
inspectResp, err := cr.cli.ContainerExecInspect(ctx, idResp.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to inspect exec: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch inspectResp.ExitCode {
|
||||||
|
case 0:
|
||||||
|
return nil
|
||||||
|
case 127:
|
||||||
|
return fmt.Errorf("exitcode '%d': command not found, please refer to https://github.com/nektos/act/issues/107 for more information", inspectResp.ExitCode)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("exitcode '%d': failure", inspectResp.ExitCode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:contextcheck
|
||||||
|
func (cr *containerReference) execExt(cmd []string, env map[string]string, user, workdir string) common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
// Fix slashes when running on Windows
|
done := make(chan error)
|
||||||
if runtime.GOOS == "windows" {
|
go func() {
|
||||||
var newCmd []string
|
defer func() {
|
||||||
for _, v := range cmd {
|
done <- errors.New("invalid Operation")
|
||||||
newCmd = append(newCmd, strings.ReplaceAll(v, `\`, `/`))
|
}()
|
||||||
|
done <- cr.exec(ctx, cmd, env, user, workdir)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
timed, cancelTimed := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||||
|
defer cancelTimed()
|
||||||
|
err := cr.cli.ContainerKill(timed, cr.id, "kill")
|
||||||
|
if err != nil {
|
||||||
|
logger.Error(err)
|
||||||
}
|
}
|
||||||
cmd = newCmd
|
_ = cr.start()(timed)
|
||||||
}
|
logger.Info("This step was cancelled")
|
||||||
|
return fmt.Errorf("this step was cancelled: %w", ctx.Err())
|
||||||
logger.Debugf("Exec command '%s'", cmd)
|
case ret := <-done:
|
||||||
isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
|
return ret
|
||||||
envList := make([]string, 0)
|
|
||||||
for k, v := range env {
|
|
||||||
envList = append(envList, fmt.Sprintf("%s=%s", k, v))
|
|
||||||
}
|
|
||||||
|
|
||||||
var wd string
|
|
||||||
if workdir != "" {
|
|
||||||
if strings.HasPrefix(workdir, "/") {
|
|
||||||
wd = workdir
|
|
||||||
} else {
|
|
||||||
wd = fmt.Sprintf("%s/%s", cr.input.WorkingDir, workdir)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
wd = cr.input.WorkingDir
|
|
||||||
}
|
|
||||||
logger.Debugf("Working directory '%s'", wd)
|
|
||||||
|
|
||||||
idResp, err := cr.cli.ContainerExecCreate(ctx, cr.id, container.ExecOptions{
|
|
||||||
User: user,
|
|
||||||
Cmd: cmd,
|
|
||||||
WorkingDir: wd,
|
|
||||||
Env: envList,
|
|
||||||
Tty: isTerminal,
|
|
||||||
AttachStderr: true,
|
|
||||||
AttachStdout: true,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to create exec: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := cr.cli.ContainerExecAttach(ctx, idResp.ID, container.ExecStartOptions{
|
|
||||||
Tty: isTerminal,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to attach to exec: %w", err)
|
|
||||||
}
|
|
||||||
defer resp.Close()
|
|
||||||
|
|
||||||
err = cr.waitForCommand(ctx, isTerminal, resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
inspectResp, err := cr.cli.ContainerExecInspect(ctx, idResp.ID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to inspect exec: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch inspectResp.ExitCode {
|
|
||||||
case 0:
|
|
||||||
return nil
|
|
||||||
case 127:
|
|
||||||
return fmt.Errorf("exitcode '%d': command not found, please refer to https://github.com/actions-oss/act-cli/issues/107 for more information", inspectResp.ExitCode)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("exitcode '%d': failure", inspectResp.ExitCode)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -842,7 +868,7 @@ func (cr *containerReference) attach() common.Executor {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to attach to container: %w", err)
|
return fmt.Errorf("failed to attach to container: %w", err)
|
||||||
}
|
}
|
||||||
isTerminal := term.IsTerminal(int(os.Stdout.Fd()))
|
isTerminal := containerAllocateTerminal
|
||||||
|
|
||||||
var outWriter io.Writer
|
var outWriter io.Writer
|
||||||
outWriter = cr.input.Stdout
|
outWriter = cr.input.Stdout
|
||||||
|
|||||||
@@ -83,6 +83,16 @@ func (m *mockDockerClient) CopyToContainer(ctx context.Context, id string, path
|
|||||||
return args.Error(0)
|
return args.Error(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *mockDockerClient) ContainerKill(ctx context.Context, containerID string, signal string) error {
|
||||||
|
args := m.Called(ctx, containerID, signal)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockDockerClient) ContainerStart(ctx context.Context, containerID string, options container.StartOptions) error {
|
||||||
|
args := m.Called(ctx, containerID, options)
|
||||||
|
return args.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
type endlessReader struct {
|
type endlessReader struct {
|
||||||
io.Reader
|
io.Reader
|
||||||
}
|
}
|
||||||
@@ -117,6 +127,8 @@ func TestDockerExecAbort(t *testing.T) {
|
|||||||
Conn: conn,
|
Conn: conn,
|
||||||
Reader: bufio.NewReader(endlessReader{}),
|
Reader: bufio.NewReader(endlessReader{}),
|
||||||
}, nil)
|
}, nil)
|
||||||
|
client.On("ContainerKill", mock.Anything, "123", "kill").Return(nil)
|
||||||
|
client.On("ContainerStart", mock.Anything, "123", mock.AnythingOfType("container.StartOptions")).Return(nil)
|
||||||
|
|
||||||
cr := &containerReference{
|
cr := &containerReference{
|
||||||
id: "123",
|
id: "123",
|
||||||
@@ -129,7 +141,7 @@ func TestDockerExecAbort(t *testing.T) {
|
|||||||
channel := make(chan error)
|
channel := make(chan error)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
channel <- cr.exec([]string{""}, map[string]string{}, "user", "workdir")(ctx)
|
channel <- cr.execExt([]string{""}, map[string]string{}, "user", "workdir")(ctx)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
time.Sleep(500 * time.Millisecond)
|
time.Sleep(500 * time.Millisecond)
|
||||||
@@ -166,7 +178,7 @@ func TestDockerExecFailure(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := cr.exec([]string{""}, map[string]string{}, "user", "workdir")(ctx)
|
err := cr.execExt([]string{""}, map[string]string{}, "user", "workdir")(ctx)
|
||||||
assert.Error(t, err, "exit with `FAILURE`: 1")
|
assert.Error(t, err, "exit with `FAILURE`: 1")
|
||||||
|
|
||||||
conn.AssertExpectations(t)
|
conn.AssertExpectations(t)
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline st
|
|||||||
tty.Close()
|
tty.Close()
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
if true /* allocate Terminal */ {
|
if containerAllocateTerminal /* allocate Terminal */ {
|
||||||
var err error
|
var err error
|
||||||
ppty, tty, err = setupPty(cmd, cmdline)
|
ppty, tty, err = setupPty(cmd, cmdline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user