summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEarl Warren <contact@earl-warren.org>2023-03-12 14:26:24 +0100
committerEarl Warren <contact@earl-warren.org>2024-03-11 09:23:41 +0100
commitd5915243add8fa8f5179aca9cb5c2ae1be95228a (patch)
treea0cc58b3addabbf44e42def39b6c162e2ad44427
parent[FORGEJO] sync lxc-helpers 231215c11d38df793521766dcce0e80e824614ca (diff)
downloadforgejo-act-d5915243add8fa8f5179aca9cb5c2ae1be95228a.tar.xz
forgejo-act-d5915243add8fa8f5179aca9cb5c2ae1be95228a.zip
[FORGEJO] wrap self-hosted platform steps in an LXC container
act PR https://github.com/nektos/act/pull/1682 * shell script to start the LXC container * create and destroy a LXC container * run commands with lxc-attach * expose additional devices for docker & libvirt to work * install node 16 & git for checkout to work [FORGEJO] start/stop lxc working directory is /tmp [FORGEJO] use lxc-helpers to create/destroy containers [FORGEJO] do not setup LXC (cherry picked from commit c2eaf440f591cc8800ff59fef9e155d2904e0e37) Conflicts: pkg/container/host_environment.go Conflicts: pkg/container/host_environment.go [FORGJEO] upgrade to node20
-rw-r--r--pkg/container/executions_environment.go2
-rw-r--r--pkg/container/host_environment.go33
-rw-r--r--pkg/container/linux_container_environment_extensions.go11
-rw-r--r--pkg/runner/run_context.go147
4 files changed, 183 insertions, 10 deletions
diff --git a/pkg/container/executions_environment.go b/pkg/container/executions_environment.go
index 41e3b57..87e652b 100644
--- a/pkg/container/executions_environment.go
+++ b/pkg/container/executions_environment.go
@@ -5,6 +5,8 @@ import "context"
type ExecutionsEnvironment interface {
Container
ToContainerPath(string) string
+ GetName() string
+ GetRoot() string
GetActPath() string
GetPathVariableName() string
DefaultPathVariable() string
diff --git a/pkg/container/host_environment.go b/pkg/container/host_environment.go
index bdda16c..7093c06 100644
--- a/pkg/container/host_environment.go
+++ b/pkg/container/host_environment.go
@@ -26,16 +26,18 @@ import (
)
type HostEnvironment struct {
+ Name string
Path string
TmpDir string
ToolCache string
Workdir string
ActPath string
+ Root string
CleanUp func()
StdOut io.Writer
}
-func (e *HostEnvironment) Create(_ []string, _ []string) common.Executor {
+func (e *HostEnvironment) Create(_, _ []string) common.Executor {
return func(ctx context.Context) error {
return nil
}
@@ -94,7 +96,7 @@ func (e *HostEnvironment) CopyTarStream(ctx context.Context, destPath string, ta
}
}
-func (e *HostEnvironment) CopyDir(destPath string, srcPath string, useGitIgnore bool) common.Executor {
+func (e *HostEnvironment) CopyDir(destPath, srcPath string, useGitIgnore bool) common.Executor {
return func(ctx context.Context) error {
logger := common.Logger(ctx)
srcPrefix := filepath.Dir(srcPath)
@@ -288,7 +290,7 @@ func getEnvListFromMap(env map[string]string) []string {
return envList
}
-func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline string, env map[string]string, _, workdir string) error {
+func (e *HostEnvironment) exec(ctx context.Context, commandparam []string, cmdline string, env map[string]string, user, workdir string) error {
envList := getEnvListFromMap(env)
var wd string
if workdir != "" {
@@ -300,6 +302,19 @@ func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline st
} else {
wd = e.Path
}
+ if _, err := os.Stat(wd); err != nil {
+ common.Logger(ctx).Debugf("Failed to stat working directory %s %v\n", wd, err.Error())
+ }
+
+ command := make([]string, len(commandparam))
+ copy(command, commandparam)
+ if user == "root" {
+ command = append([]string{"/usr/bin/sudo"}, command...)
+ } else {
+ common.Logger(ctx).Debugf("lxc-attach --name %v %v", e.Name, command)
+ command = append([]string{"/usr/bin/sudo", "--preserve-env", "--preserve-env=PATH", "/usr/bin/lxc-attach", "--keep-env", "--name", e.Name, "--"}, command...)
+ }
+
f, err := lookupPathHost(command[0], env, e.StdOut)
if err != nil {
return err
@@ -342,7 +357,7 @@ func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline st
}
err = cmd.Run()
if err != nil {
- return err
+ return fmt.Errorf("RUN %w", err)
}
if tty != nil {
writer.AutoStop = true
@@ -399,6 +414,14 @@ func (e *HostEnvironment) ToContainerPath(path string) string {
return path
}
+func (e *HostEnvironment) GetName() string {
+ return e.Name
+}
+
+func (e *HostEnvironment) GetRoot() string {
+ return e.Root
+}
+
func (e *HostEnvironment) GetActPath() string {
actPath := e.ActPath
if runtime.GOOS == "windows" {
@@ -458,7 +481,7 @@ func (e *HostEnvironment) GetRunnerContext(_ context.Context) map[string]interfa
}
}
-func (e *HostEnvironment) ReplaceLogWriter(stdout io.Writer, _ io.Writer) (io.Writer, io.Writer) {
+func (e *HostEnvironment) ReplaceLogWriter(stdout, _ io.Writer) (io.Writer, io.Writer) {
org := e.StdOut
e.StdOut = stdout
return org, org
diff --git a/pkg/container/linux_container_environment_extensions.go b/pkg/container/linux_container_environment_extensions.go
index d673451..3518554 100644
--- a/pkg/container/linux_container_environment_extensions.go
+++ b/pkg/container/linux_container_environment_extensions.go
@@ -10,8 +10,7 @@ import (
log "github.com/sirupsen/logrus"
)
-type LinuxContainerEnvironmentExtensions struct {
-}
+type LinuxContainerEnvironmentExtensions struct{}
// Resolves the equivalent host path inside the container
// This is required for windows and WSL 2 to translate things like C:\Users\Myproject to /mnt/users/Myproject
@@ -47,6 +46,14 @@ func (*LinuxContainerEnvironmentExtensions) ToContainerPath(path string) string
return result
}
+func (*LinuxContainerEnvironmentExtensions) GetName() string {
+ return "NAME"
+}
+
+func (*LinuxContainerEnvironmentExtensions) GetRoot() string {
+ return "/var/run"
+}
+
func (*LinuxContainerEnvironmentExtensions) GetActPath() string {
return "/var/run/act"
}
diff --git a/pkg/runner/run_context.go b/pkg/runner/run_context.go
index 5c8187a..daa8b33 100644
--- a/pkg/runner/run_context.go
+++ b/pkg/runner/run_context.go
@@ -3,9 +3,11 @@ package runner
import (
"archive/tar"
"bufio"
+ "bytes"
"context"
"crypto/rand"
"crypto/sha256"
+ _ "embed"
"encoding/hex"
"encoding/json"
"errors"
@@ -16,6 +18,7 @@ import (
"regexp"
"runtime"
"strings"
+ "text/template"
"github.com/docker/go-connections/nat"
"github.com/nektos/act/pkg/common"
@@ -183,6 +186,94 @@ func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string) {
return binds, mounts
}
+//go:embed lxc-helpers-lib.sh
+var lxcHelpersLib string
+
+//go:embed lxc-helpers.sh
+var lxcHelpers string
+
+var startTemplate = template.Must(template.New("start").Parse(`#!/bin/bash -e
+source $(dirname $0)/lxc-helpers-lib.sh
+
+LXC_CONTAINER_RELEASE="{{.Release}}"
+
+function template_act() {
+ echo $(lxc_template_release)-act
+}
+
+function install_nodejs() {
+ local name="$1"
+
+ local script=/usr/local/bin/lxc-helpers-install-node.sh
+
+ cat > $(lxc_root $name)/$script <<'EOF'
+#!/bin/sh -e
+# https://github.com/nodesource/distributions#debinstall
+export DEBIAN_FRONTEND=noninteractive
+apt-get install -qq -y ca-certificates curl gnupg git
+mkdir -p /etc/apt/keyrings
+curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
+NODE_MAJOR=20
+echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
+apt-get update -qq
+apt-get install -qq -y nodejs
+EOF
+ lxc_container_run_script $name $script
+}
+
+function build_template_act() {
+ local name="$(template_act)"
+
+ if lxc_exists $name ; then
+ return
+ fi
+
+ lxc_build_template $(lxc_template_release) $name
+ lxc_container_start $name
+ install_nodejs $name
+ lxc_container_stop $name
+}
+
+lxc_prepare_environment
+build_template_act
+lxc_build_template $(template_act) "{{.Name}}"
+lxc_container_mount "{{.Name}}" "{{ .Root }}"
+lxc_container_start "{{.Name}}"
+`))
+
+var stopTemplate = template.Must(template.New("stop").Parse(`#!/bin/bash
+source $(dirname $0)/lxc-helpers-lib.sh
+
+lxc_container_destroy "{{.Name}}"
+`))
+
+func (rc *RunContext) stopHostEnvironment() common.Executor {
+ return func(ctx context.Context) error {
+ logger := common.Logger(ctx)
+ logger.Debugf("stopHostEnvironment")
+
+ var stopScript bytes.Buffer
+ if err := stopTemplate.Execute(&stopScript, struct {
+ Name string
+ Root string
+ }{
+ Name: rc.JobContainer.GetName(),
+ Root: rc.JobContainer.GetRoot(),
+ }); err != nil {
+ return err
+ }
+
+ return common.NewPipelineExecutor(
+ rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
+ Name: "workflow/stop-lxc.sh",
+ Mode: 0755,
+ Body: stopScript.String(),
+ }),
+ rc.JobContainer.Exec([]string{rc.JobContainer.GetActPath() + "/workflow/stop-lxc.sh"}, map[string]string{}, "root", "/tmp"),
+ )(ctx)
+ }
+}
+
func (rc *RunContext) startHostEnvironment() common.Executor {
return func(ctx context.Context) error {
logger := common.Logger(ctx)
@@ -198,7 +289,8 @@ func (rc *RunContext) startHostEnvironment() common.Executor {
cacheDir := rc.ActionCacheDir()
randBytes := make([]byte, 8)
_, _ = rand.Read(randBytes)
- miscpath := filepath.Join(cacheDir, hex.EncodeToString(randBytes))
+ randName := hex.EncodeToString(randBytes)
+ miscpath := filepath.Join(cacheDir, randName)
actPath := filepath.Join(miscpath, "act")
if err := os.MkdirAll(actPath, 0o777); err != nil {
return err
@@ -213,6 +305,8 @@ func (rc *RunContext) startHostEnvironment() common.Executor {
}
toolCache := filepath.Join(cacheDir, "tool_cache")
rc.JobContainer = &container.HostEnvironment{
+ Name: randName,
+ Root: miscpath,
Path: path,
TmpDir: runnerTmp,
ToolCache: toolCache,
@@ -238,8 +332,45 @@ func (rc *RunContext) startHostEnvironment() common.Executor {
}
}
+ var startScript bytes.Buffer
+ if err := startTemplate.Execute(&startScript, struct {
+ Name string
+ Template string
+ Release string
+ Repo string
+ Root string
+ TmpDir string
+ Script string
+ }{
+ Name: rc.JobContainer.GetName(),
+ Template: "debian",
+ Release: "bullseye",
+ Repo: "", // step.Environment["CI_REPO"],
+ Root: rc.JobContainer.GetRoot(),
+ TmpDir: runnerTmp,
+ Script: "", // "commands-" + step.Name,
+ }); err != nil {
+ return err
+ }
+
return common.NewPipelineExecutor(
rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
+ Name: "workflow/lxc-helpers-lib.sh",
+ Mode: 0755,
+ Body: lxcHelpersLib,
+ }),
+ rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
+ Name: "workflow/lxc-helpers.sh",
+ Mode: 0755,
+ Body: lxcHelpers,
+ }),
+ rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
+ Name: "workflow/start-lxc.sh",
+ Mode: 0755,
+ Body: startScript.String(),
+ }),
+ rc.JobContainer.Exec([]string{rc.JobContainer.GetActPath() + "/workflow/start-lxc.sh"}, map[string]string{}, "root", "/tmp"),
+ rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
Name: "workflow/event.json",
Mode: 0o644,
Body: rc.EventJSON,
@@ -601,12 +732,22 @@ func (rc *RunContext) IsHostEnv(ctx context.Context) bool {
}
func (rc *RunContext) stopContainer() common.Executor {
- return rc.stopJobContainer()
+ return func(ctx context.Context) error {
+ image := rc.platformImage(ctx)
+ if strings.EqualFold(image, "-self-hosted") {
+ return rc.stopHostEnvironment()(ctx)
+ }
+ return rc.stopJobContainer()(ctx)
+ }
}
func (rc *RunContext) closeContainer() common.Executor {
return func(ctx context.Context) error {
if rc.JobContainer != nil {
+ image := rc.platformImage(ctx)
+ if strings.EqualFold(image, "-self-hosted") {
+ return rc.stopHostEnvironment()(ctx)
+ }
return rc.JobContainer.Close()(ctx)
}
return nil
@@ -628,7 +769,7 @@ func (rc *RunContext) steps() []*model.Step {
// Executor returns a pipeline executor for all the steps in the job
func (rc *RunContext) Executor() (common.Executor, error) {
var executor common.Executor
- var jobType, err = rc.Run.Job().Type()
+ jobType, err := rc.Run.Job().Type()
switch jobType {
case model.JobTypeDefault: