Files
act_runner/act/runner/step_docker.go
Mirko Sekulic f2b4dbf05f run docker step in host mode (#857)
## Problem
In host executor mode, uses: docker://<image> step actions fail because
act/runner/step_docker.go always attaches the step container to the job
container's network namespace, which doesn't exist in host mode.

### Example
Run following job in host runner

```yaml
jobs:
  test:
    runs-on: ubuntu-latest-host
    steps:
      - uses: docker://alpine:3.20
        with:
          args: echo hello
```
```
Error:
  failed to start container: Error response from daemon:
    joining network namespace of container:
    No such container: xxxxxx
```

This pr allows the docker step in the host mode

## Testing
I tested following steps on host runner and it worked

```yaml

 - name: Test azure cli action in host mode
   uses: azure/cli@v2
   env:
     RUNNER_OS: Linux
   with:
     inlineScript: echo "hello from azure cli"
 
 - uses: docker://alpine:3.20
   with:
     args: echo hello

```

---------

Co-authored-by: Nicolas <bircni@icloud.com>
Reviewed-on: https://gitea.com/gitea/act_runner/pulls/857
Reviewed-by: Nicolas <bircni@icloud.com>
Co-authored-by: Mirko Sekulic <misha.sekulic@gmail.com>
Co-committed-by: Mirko Sekulic <misha.sekulic@gmail.com>
2026-04-27 15:26:31 +00:00

145 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/act_runner/act/common"
"gitea.com/gitea/act_runner/act/container"
"gitea.com/gitea/act_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: createSimpleContainerName(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,
})
return stepContainer
}