mirror of
https://gitea.com/gitea/act_runner
synced 2026-09-21 19:37:07 +02:00
enhance: add macOS VM executor backend
Replace the Tart-based prototype with a runner-oriented macOS VM executor backed by runner-executor-macos and Apple Virtualization. Assisted-by: Codet:MODEL_VERSION
This commit is contained in:
+118
-2
@@ -17,6 +17,7 @@ import (
|
||||
"io"
|
||||
maps0 "maps"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/container"
|
||||
"gitea.com/gitea/runner/act/ghcontext"
|
||||
"gitea.com/gitea/runner/internal/pkg/labels"
|
||||
"gitea.com/gitea/runner/internal/pkg/lock"
|
||||
|
||||
"gitea.dev/actionslib/pkg/exprparser"
|
||||
@@ -424,6 +426,112 @@ func (rc *RunContext) startHostEnvironment() common.Executor {
|
||||
}
|
||||
}
|
||||
|
||||
// printStartMacOSVMGroup mirrors the "Starting job container" section for macOS VMs.
|
||||
func printStartMacOSVMGroup(ctx context.Context, image, name string) func() {
|
||||
rawLogger := common.Logger(ctx).WithField(rawOutputField, true)
|
||||
rawLogger.Infof("::group::Starting job VM")
|
||||
rawLogger.Infof("image: %s", image)
|
||||
rawLogger.Infof("name: %s", name)
|
||||
return func() {
|
||||
rawLogger.Infof("::endgroup::")
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *RunContext) startMacOSVMEnvironment() common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
if len(rc.Run.Job().Services) > 0 {
|
||||
return errors.New("service containers are not supported with the macOS VM executor; see docs/macos-vm-executor.md")
|
||||
}
|
||||
|
||||
image := rc.macOSVMImage()
|
||||
logWriter := rc.commandLogWriter(ctx)
|
||||
name := rc.macOSVMName()
|
||||
rc.Env["JOB_CONTAINER_NAME"] = name
|
||||
|
||||
guestWorkdir := rc.macOSVMGuestWorkdir()
|
||||
scratch := path.Join("/", rc.macOSVMWorkdirParent(), "scratch", name)
|
||||
actPath := path.Join(scratch, "act")
|
||||
tmpDir := path.Join(scratch, "tmp")
|
||||
toolCache := rc.toolCache(path.Join(scratch, "tool_cache"))
|
||||
|
||||
jobContainer, err := container.NewMacOSVMEnvironment(container.MacOSVMEnvironmentInput{
|
||||
Path: scratch,
|
||||
TmpDir: tmpDir,
|
||||
ToolCache: toolCache,
|
||||
Workdir: rc.Config.Workdir,
|
||||
GuestWorkdir: guestWorkdir,
|
||||
ActPath: actPath,
|
||||
Image: image,
|
||||
VMName: name,
|
||||
ExecutorPath: rc.Config.MacOSVM.ExecutorPath,
|
||||
CPU: rc.Config.MacOSVM.CPU,
|
||||
Memory: rc.Config.MacOSVM.Memory,
|
||||
BootTimeout: rc.Config.MacOSVM.BootTimeout,
|
||||
Stdout: logWriter,
|
||||
Stderr: logWriter,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rc.JobContainer = jobContainer
|
||||
rc.cleanUpJobContainer = jobContainer.Remove()
|
||||
|
||||
for k, v := range rc.getRunnerContext(ctx) {
|
||||
if v, ok := v.(string); ok {
|
||||
rc.Env["RUNNER_"+strings.ToUpper(k)] = v
|
||||
}
|
||||
}
|
||||
for _, env := range os.Environ() {
|
||||
if k, v, ok := strings.Cut(env, "="); ok {
|
||||
if _, ok := rc.Env[k]; !ok {
|
||||
rc.Env[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defer printStartMacOSVMGroup(ctx, image, name)()
|
||||
return common.NewPipelineExecutor(
|
||||
jobContainer.Pull(rc.Config.ForcePull),
|
||||
jobContainer.Create(nil, nil),
|
||||
jobContainer.Start(false),
|
||||
rc.captureJobContainerInfo(),
|
||||
jobContainer.Copy(jobContainer.GetActPath()+"/", &container.FileEntry{
|
||||
Name: "workflow/event.json",
|
||||
Mode: 0o644,
|
||||
Body: rc.EventJSON,
|
||||
}, &container.FileEntry{
|
||||
Name: "workflow/envs.txt",
|
||||
Mode: 0o666,
|
||||
Body: "",
|
||||
}),
|
||||
)(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (rc *RunContext) macOSVMImage() string {
|
||||
return strings.TrimPrefix(rc.platformImage, labels.MacOSVMSchemePrefix)
|
||||
}
|
||||
|
||||
func (rc *RunContext) macOSVMName() string {
|
||||
nameParts := []string{"GITEA-MACOS-VM", rc.Config.RunnerUUID, rc.Config.ContainerNamePrefix, "WORKFLOW-" + rc.Run.Workflow.Name, "JOB-" + rc.Run.JobID, rc.Name}
|
||||
if rc.caller != nil {
|
||||
nameParts = append(nameParts, "CALLED-BY-"+rc.caller.runContext.JobName)
|
||||
}
|
||||
return createContainerName(nameParts...)
|
||||
}
|
||||
|
||||
func (rc *RunContext) macOSVMGuestWorkdir() string {
|
||||
rel := strings.TrimLeft(filepath.ToSlash(rc.Config.Workdir), "/")
|
||||
return path.Join("/", rc.macOSVMWorkdirParent(), rel)
|
||||
}
|
||||
|
||||
func (rc *RunContext) macOSVMWorkdirParent() string {
|
||||
if rc.Config.MacOSVM.WorkdirParent != "" {
|
||||
return rc.Config.MacOSVM.WorkdirParent
|
||||
}
|
||||
return "/Users/admin/runner"
|
||||
}
|
||||
|
||||
// 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() {
|
||||
@@ -980,9 +1088,12 @@ func (rc *RunContext) interpolateOutputs() common.Executor {
|
||||
func (rc *RunContext) startContainer() common.Executor {
|
||||
return func(ctx context.Context) error {
|
||||
var err error
|
||||
if rc.IsHostEnv() {
|
||||
switch {
|
||||
case rc.IsHostEnv():
|
||||
err = rc.startHostEnvironment()(ctx)
|
||||
} else {
|
||||
case rc.IsMacOSVMEnv():
|
||||
err = rc.startMacOSVMEnvironment()(ctx)
|
||||
default:
|
||||
err = rc.startJobContainer()(ctx)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -1014,6 +1125,11 @@ func (rc *RunContext) IsHostEnv() bool {
|
||||
return strings.EqualFold(rc.platformImage, "-self-hosted")
|
||||
}
|
||||
|
||||
// IsMacOSVMEnv reports whether the resolved platform image requests a macOS VM.
|
||||
func (rc *RunContext) IsMacOSVMEnv() bool {
|
||||
return strings.HasPrefix(rc.platformImage, labels.MacOSVMSchemePrefix)
|
||||
}
|
||||
|
||||
func (rc *RunContext) stopContainer() common.Executor {
|
||||
return rc.stopJobContainer()
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
"gitea.com/gitea/runner/act/common"
|
||||
"gitea.com/gitea/runner/act/container"
|
||||
"gitea.com/gitea/runner/internal/pkg/labels"
|
||||
|
||||
"gitea.dev/actionslib/pkg/exprparser"
|
||||
"gitea.dev/actionslib/pkg/model"
|
||||
@@ -1458,3 +1459,25 @@ func TestRunContextWithGithubEnvRunnerValues(t *testing.T) {
|
||||
assert.Equal(t, "/workspace/owner", env["RUNNER_WORKSPACE"])
|
||||
assert.Equal(t, "1", env["RUNNER_DEBUG"])
|
||||
}
|
||||
|
||||
func TestRunContextMacOSVMPlatform(t *testing.T) {
|
||||
rc := createRunsOnRunContext(t, "macos-latest")
|
||||
rc.Config.PlatformPicker = func([]string) string {
|
||||
return labels.MacOSVMSchemePrefix + "ghcr.io/cirruslabs/macos-sonoma-base:latest"
|
||||
}
|
||||
require.NoError(t, rc.resolvePlatformImage(t.Context()))
|
||||
|
||||
assert.True(t, rc.IsMacOSVMEnv())
|
||||
assert.False(t, rc.IsHostEnv())
|
||||
assert.Equal(t, "ghcr.io/cirruslabs/macos-sonoma-base:latest", rc.macOSVMImage())
|
||||
}
|
||||
|
||||
func TestRunContextMacOSVMGuestWorkdir(t *testing.T) {
|
||||
rc := createRunsOnRunContext(t, "macos-latest")
|
||||
rc.Config.Workdir = "/workspace/owner/repo"
|
||||
rc.Config.MacOSVM.WorkdirParent = "/Users/admin/runner"
|
||||
rc.Config.RunnerUUID = "runner-1"
|
||||
|
||||
assert.Equal(t, "/Users/admin/runner/workspace/owner/repo", rc.macOSVMGuestWorkdir())
|
||||
assert.Contains(t, rc.macOSVMName(), "GITEA-MACOS-VM-runner-1-")
|
||||
}
|
||||
|
||||
@@ -79,9 +79,20 @@ type Config struct {
|
||||
MaxParallel int // max parallel jobs to run across all workflows (0 = no limit, uses CPU count)
|
||||
AllocatePTY bool // allocate a pseudo-TTY for each step's process
|
||||
ServiceReadyTimeout time.Duration // how long a job waits for its service containers to report healthy (0 uses the default)
|
||||
RunnerUUID string // UUID this runner registered with, used to identify its macOS VMs
|
||||
RunnerName string // name this runner registered with, reported as `runner.name`, defaults to the hostname
|
||||
JobStartedHook string // script run inside the job environment before the job's first step; ACTIONS_RUNNER_HOOK_JOB_STARTED is read from Env when empty
|
||||
JobCompletedHook string // script run inside the job environment after the job's last step; ACTIONS_RUNNER_HOOK_JOB_COMPLETED is read from Env when empty
|
||||
MacOSVM MacOSVMConfig // configures the macOS VM executor
|
||||
}
|
||||
|
||||
// MacOSVMConfig contains the macOS VM executor settings that come from the runner config.
|
||||
type MacOSVMConfig struct {
|
||||
ExecutorPath string // path to the runner-executor-macos binary
|
||||
WorkdirParent string // guest directory under which the job workspace is created
|
||||
CPU int // number of virtual CPUs, 0 keeps the image default
|
||||
Memory int // VM memory in megabytes, 0 keeps the image default
|
||||
BootTimeout time.Duration // how long to wait for the VM to become ready
|
||||
}
|
||||
|
||||
// RunnerDebug reports whether debug logging is on, exposed as `runner.debug` and
|
||||
|
||||
Reference in New Issue
Block a user