Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: tart macOS vm's as job container #2434

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/avast/retry-go v3.0.0+incompatible
github.com/cloudflare/circl v1.3.7 // indirect
github.com/containerd/containerd v1.7.13 // indirect
github.com/containerd/log v0.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
Expand Down
9 changes: 9 additions & 0 deletions pkg/runner/run_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,9 @@ func (rc *RunContext) startContainer() common.Executor {
if rc.IsHostEnv(ctx) {
return rc.startHostEnvironment()(ctx)
}
if rc.IsTartEnv(ctx) {
return rc.startTartEnvironment()(ctx)
}
return rc.startJobContainer()(ctx)
}
}
Expand All @@ -657,6 +660,12 @@ func (rc *RunContext) IsHostEnv(ctx context.Context) bool {
return image == "" && strings.EqualFold(platform, "-self-hosted")
}

func (rc *RunContext) IsTartEnv(ctx context.Context) bool {
platform := rc.runsOnImage(ctx)
image := rc.containerImage(ctx)
return image == "" && strings.HasPrefix(platform, "tart://")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like -self-hosted, but with tart://. I forget this variant has only been used in act_runner as of now, but looks better.

}

func (rc *RunContext) stopContainer() common.Executor {
return rc.stopJobContainer()
}
Expand Down
111 changes: 111 additions & 0 deletions pkg/runner/run_context_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package runner

import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/nektos/act/pkg/common"
"github.com/nektos/act/pkg/container"
"github.com/nektos/act/pkg/tart"
)

func (rc *RunContext) startTartEnvironment() common.Executor {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This started as a copy of hostenvironment, but is now different

return func(ctx context.Context) error {
logger := common.Logger(ctx)
rawLogger := logger.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
})
cacheDir := rc.ActionCacheDir()
randBytes := make([]byte, 8)
_, _ = rand.Read(randBytes)
miscpath := filepath.Join(cacheDir, hex.EncodeToString(randBytes))
actPath := filepath.Join(miscpath, "act")
if err := os.MkdirAll(actPath, 0o777); err != nil {
Copy link

@allexiusw allexiusw Oct 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should 0o777 be added as a constant? Maybe FULL_RWX or FULL_PERMS

return err
}
path := filepath.Join(miscpath, "hostexecutor")
if err := os.MkdirAll(path, 0o777); err != nil {
return err
}
runnerTmp := filepath.Join(miscpath, "tmp")
if err := os.MkdirAll(runnerTmp, 0o777); err != nil {
return err
}
toolCache := filepath.Join(cacheDir, "tool_cache")
platImage := rc.runsOnImage(ctx)
platURI, _ := url.Parse(platImage)
query := platURI.Query()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought that I'm smart, but no providing options in the -P <label>=tart://test?... query doesn't actually work so only defaults for now

tenv := &tart.Environment{
HostEnvironment: container.HostEnvironment{
Path: path,
TmpDir: runnerTmp,
ToolCache: toolCache,
Workdir: rc.Config.Workdir,
ActPath: actPath,
CleanUp: func() {
os.RemoveAll(miscpath)
},
StdOut: logWriter,
},
Config: tart.Config{
SSHUsername: "admin",
SSHPassword: "admin",
Softnet: query.Get("softnet") == "1",
Headless: query.Get("headless") != "0",
AlwaysPull: query.Get("pull") != "0",
},
Env: &tart.Env{
JobImage: platURI.Host + platURI.EscapedPath(),
JobID: rc.jobContainerName(),
},
Miscpath: miscpath,
}
rc.JobContainer = tenv
if query.Has("sshusername") {
tenv.Config.SSHUsername = query.Get("sshusername")
}
if query.Has("sshpassword") {
tenv.Config.SSHPassword = query.Get("sshpassword")
}
rc.cleanUpJobContainer = rc.JobContainer.Remove()
for k, v := range rc.JobContainer.GetRunnerContext(ctx) {
if v, ok := v.(string); ok {
rc.Env[fmt.Sprintf("RUNNER_%s", strings.ToUpper(k))] = v
}
}
// for _, env := range os.Environ() {
// if k, v, ok := strings.Cut(env, "="); ok {
// // don't override
// if _, ok := rc.Env[k]; !ok {
// rc.Env[k] = v
// }
// }
// }

return common.NewPipelineExecutor(
// rc.JobContainer.Remove(),
rc.JobContainer.Start(false),
rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
Name: "workflow/event.json",
Mode: 0o644,
Body: rc.EventJSON,
}, &container.FileEntry{
Name: "workflow/envs.txt",
Mode: 0o666,
Body: "",
}),
)(ctx)
}
}
16 changes: 16 additions & 0 deletions pkg/runner/run_context_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//go:build !darwin

package runner

import (
"context"
"fmt"

"github.com/nektos/act/pkg/common"
)

func (rc *RunContext) startTartEnvironment() common.Executor {
return func(_ context.Context) error {
return fmt.Errorf("You need macOS for tart")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone thinks to run this mode on another OS, abort as tart is not available and some syscall part doesn't compile anyway.

This comment was marked as outdated.

This comment was marked as outdated.

}
}
27 changes: 27 additions & 0 deletions pkg/runner/runner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,33 @@ func TestRunEvent(t *testing.T) {
}
}

func TestTartNotSupportedOnNonDarwin(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}

ctx := context.Background()

tables := []TestJobFileInfo{}

if runtime.GOOS != "darwin" {
platforms := map[string]string{
"ubuntu-latest": "tart://ghcr.io/cirruslabs/macos-sonoma-base:latest",
}

tables = append(tables, []TestJobFileInfo{
// Shells
{workdir, "basic", "push", "tart not supported", platforms, secrets},
}...)
}

for _, table := range tables {
t.Run(table.workflowPath, func(t *testing.T) {
table.runTest(ctx, t, &Config{})
})
}
}

func TestRunEventHostEnvironment(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
Expand Down
9 changes: 9 additions & 0 deletions pkg/tart/config_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package tart

type Config struct {
SSHUsername string
SSHPassword string
Softnet bool
Headless bool
AlwaysPull bool
}
18 changes: 18 additions & 0 deletions pkg/tart/env_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package tart

type Env struct {
JobID string
JobImage string
FailureExitCode int
Registry *Registry
}

type Registry struct {
Address string
User string
Password string
}

func (e Env) VirtualMachineID() string {
return e.JobID
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm reusing the docker container names used by containers, so no prefix and so on

}
Loading
Loading