Skip to content

Commit 677e073

Browse files
feat: tart macOS vm's as job container (#33)
adds the tart:// protocol to platform mapping e.g. `-P macos-14=tart://ghcr.io/cirruslabs/macos-sonoma-base:latest` if you have a mac. `add-path` is probably broken
1 parent 592dc4b commit 677e073

10 files changed

Lines changed: 832 additions & 26 deletions

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ require (
3636

3737
require (
3838
dario.cat/mergo v1.0.1
39+
github.com/avast/retry-go v3.0.0+incompatible
3940
github.com/distribution/reference v0.6.0
4041
github.com/golang-jwt/jwt/v5 v5.2.1
42+
golang.org/x/crypto v0.32.0
4143
google.golang.org/protobuf v1.36.4
4244
)
4345

@@ -98,7 +100,6 @@ require (
98100
go.opentelemetry.io/otel/metric v1.33.0 // indirect
99101
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
100102
go.opentelemetry.io/otel/trace v1.33.0 // indirect
101-
golang.org/x/crypto v0.32.0 // indirect
102103
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
103104
golang.org/x/net v0.34.0 // indirect
104105
golang.org/x/sync v0.10.0 // indirect

go.sum

Lines changed: 138 additions & 25 deletions
Large diffs are not rendered by default.

pkg/runner/run_context.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,9 @@ func (rc *RunContext) startContainer() common.Executor {
655655
if rc.IsHostEnv(ctx) {
656656
return rc.startHostEnvironment()(ctx)
657657
}
658+
if rc.IsTartEnv(ctx) {
659+
return rc.startTartEnvironment()(ctx)
660+
}
658661
return rc.startJobContainer()(ctx)
659662
}
660663
}
@@ -665,6 +668,12 @@ func (rc *RunContext) IsHostEnv(ctx context.Context) bool {
665668
return image == "" && strings.EqualFold(platform, "-self-hosted")
666669
}
667670

671+
func (rc *RunContext) IsTartEnv(ctx context.Context) bool {
672+
platform := rc.runsOnImage(ctx)
673+
image := rc.containerImage(ctx)
674+
return image == "" && strings.HasPrefix(platform, "tart://")
675+
}
676+
668677
func (rc *RunContext) stopContainer() common.Executor {
669678
return rc.stopJobContainer()
670679
}

pkg/runner/run_context_darwin.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package runner
2+
3+
import (
4+
"context"
5+
"crypto/rand"
6+
"encoding/hex"
7+
"fmt"
8+
"net/url"
9+
"os"
10+
"path/filepath"
11+
"strings"
12+
13+
"github.com/actions-oss/act-cli/pkg/common"
14+
"github.com/actions-oss/act-cli/pkg/container"
15+
"github.com/actions-oss/act-cli/pkg/tart"
16+
)
17+
18+
func (rc *RunContext) startTartEnvironment() common.Executor {
19+
return func(ctx context.Context) error {
20+
logger := common.Logger(ctx)
21+
rawLogger := logger.WithField("raw_output", true)
22+
logWriter := common.NewLineWriter(rc.commandHandler(ctx), func(s string) bool {
23+
if rc.Config.LogOutput {
24+
rawLogger.Infof("%s", s)
25+
} else {
26+
rawLogger.Debugf("%s", s)
27+
}
28+
return true
29+
})
30+
cacheDir := rc.ActionCacheDir()
31+
randBytes := make([]byte, 8)
32+
_, _ = rand.Read(randBytes)
33+
miscpath := filepath.Join(cacheDir, hex.EncodeToString(randBytes))
34+
actPath := filepath.Join(miscpath, "act")
35+
if err := os.MkdirAll(actPath, 0o777); err != nil {
36+
return err
37+
}
38+
path := filepath.Join(miscpath, "hostexecutor")
39+
if err := os.MkdirAll(path, 0o777); err != nil {
40+
return err
41+
}
42+
runnerTmp := filepath.Join(miscpath, "tmp")
43+
if err := os.MkdirAll(runnerTmp, 0o777); err != nil {
44+
return err
45+
}
46+
toolCache := filepath.Join(cacheDir, "tool_cache")
47+
platImage := rc.runsOnImage(ctx)
48+
platURI, _ := url.Parse(platImage)
49+
query := platURI.Query()
50+
tenv := &tart.Environment{
51+
HostEnvironment: container.HostEnvironment{
52+
Path: path,
53+
TmpDir: runnerTmp,
54+
ToolCache: toolCache,
55+
Workdir: rc.Config.Workdir,
56+
ActPath: actPath,
57+
CleanUp: func() {
58+
os.RemoveAll(miscpath)
59+
},
60+
StdOut: logWriter,
61+
},
62+
Config: tart.Config{
63+
SSHUsername: "admin",
64+
SSHPassword: "admin",
65+
Softnet: query.Get("softnet") == "1",
66+
Headless: query.Get("headless") != "0",
67+
AlwaysPull: query.Get("pull") != "0",
68+
},
69+
Env: &tart.Env{
70+
JobImage: platURI.Host + platURI.EscapedPath(),
71+
JobID: rc.jobContainerName(),
72+
},
73+
Miscpath: miscpath,
74+
}
75+
rc.JobContainer = tenv
76+
if query.Has("sshusername") {
77+
tenv.Config.SSHUsername = query.Get("sshusername")
78+
}
79+
if query.Has("sshpassword") {
80+
tenv.Config.SSHPassword = query.Get("sshpassword")
81+
}
82+
rc.cleanUpJobContainer = rc.JobContainer.Remove()
83+
for k, v := range rc.JobContainer.GetRunnerContext(ctx) {
84+
if v, ok := v.(string); ok {
85+
rc.Env[fmt.Sprintf("RUNNER_%s", strings.ToUpper(k))] = v
86+
}
87+
}
88+
// for _, env := range os.Environ() {
89+
// if k, v, ok := strings.Cut(env, "="); ok {
90+
// // don't override
91+
// if _, ok := rc.Env[k]; !ok {
92+
// rc.Env[k] = v
93+
// }
94+
// }
95+
// }
96+
97+
return common.NewPipelineExecutor(
98+
// rc.JobContainer.Remove(),
99+
rc.JobContainer.Start(false),
100+
rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
101+
Name: "workflow/event.json",
102+
Mode: 0o644,
103+
Body: rc.EventJSON,
104+
}, &container.FileEntry{
105+
Name: "workflow/envs.txt",
106+
Mode: 0o666,
107+
Body: "",
108+
}),
109+
)(ctx)
110+
}
111+
}

pkg/runner/run_context_other.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//go:build !darwin
2+
3+
package runner
4+
5+
import (
6+
"context"
7+
"fmt"
8+
9+
"github.com/actions-oss/act-cli/pkg/common"
10+
)
11+
12+
func (rc *RunContext) startTartEnvironment() common.Executor {
13+
return func(_ context.Context) error {
14+
return fmt.Errorf("You need macOS for tart")
15+
}
16+
}

pkg/runner/runner_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,33 @@ func TestRunEvent(t *testing.T) {
358358
}
359359
}
360360

361+
func TestTartNotSupportedOnNonDarwin(t *testing.T) {
362+
if testing.Short() {
363+
t.Skip("skipping integration test")
364+
}
365+
366+
ctx := context.Background()
367+
368+
tables := []TestJobFileInfo{}
369+
370+
if runtime.GOOS != "darwin" {
371+
platforms := map[string]string{
372+
"ubuntu-latest": "tart://ghcr.io/cirruslabs/macos-sonoma-base:latest",
373+
}
374+
375+
tables = append(tables, []TestJobFileInfo{
376+
// Shells
377+
{workdir, "basic", "push", "tart not supported", platforms, secrets},
378+
}...)
379+
}
380+
381+
for _, table := range tables {
382+
t.Run(table.workflowPath, func(t *testing.T) {
383+
table.runTest(ctx, t, &Config{})
384+
})
385+
}
386+
}
387+
361388
func TestRunEventHostEnvironment(t *testing.T) {
362389
if testing.Short() {
363390
t.Skip("skipping integration test")

pkg/tart/config_darwin.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package tart
2+
3+
type Config struct {
4+
SSHUsername string
5+
SSHPassword string
6+
Softnet bool
7+
Headless bool
8+
AlwaysPull bool
9+
}

pkg/tart/env_darwin.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package tart
2+
3+
type Env struct {
4+
JobID string
5+
JobImage string
6+
FailureExitCode int
7+
Registry *Registry
8+
}
9+
10+
type Registry struct {
11+
Address string
12+
User string
13+
Password string
14+
}
15+
16+
func (e Env) VirtualMachineID() string {
17+
return e.JobID
18+
}

0 commit comments

Comments
 (0)