[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
This commit is contained in:
Earl Warren 2023-03-12 14:26:24 +01:00
parent 9ede3e638c
commit c2eaf440f5
No known key found for this signature in database
GPG key ID: 0579CB2928A78A00
4 changed files with 177 additions and 9 deletions

View file

@ -5,6 +5,8 @@ import "context"
type ExecutionsEnvironment interface {
Container
ToContainerPath(string) string
GetName() string
GetRoot() string
GetActPath() string
GetPathVariableName() string
DefaultPathVariable() string

View file

@ -25,16 +25,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(capAdd []string, capDrop []string) common.Executor {
func (e *HostEnvironment) Create(capAdd, capDrop []string) common.Executor {
return func(ctx context.Context) error {
return nil
}
@ -66,7 +68,7 @@ func (e *HostEnvironment) Copy(destPath string, files ...*FileEntry) common.Exec
}
}
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)
@ -260,7 +262,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, user, 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 != "" {
@ -272,6 +274,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
@ -314,7 +329,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
@ -367,6 +382,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" {
@ -426,7 +449,7 @@ func (e *HostEnvironment) GetRunnerContext(ctx context.Context) map[string]inter
}
}
func (e *HostEnvironment) ReplaceLogWriter(stdout io.Writer, stderr io.Writer) (io.Writer, io.Writer) {
func (e *HostEnvironment) ReplaceLogWriter(stdout, stderr io.Writer) (io.Writer, io.Writer) {
org := e.StdOut
e.StdOut = stdout
return org, org

View file

@ -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"
}

View file

@ -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"
"time"
"github.com/opencontainers/selinux/go-selinux"
@ -178,6 +181,89 @@ 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 -xe
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 -xe
# https://github.com/nodesource/distributions#debinstall
apt-get install -qq -y curl git
curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
apt-get install -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 -x
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)
@ -193,7 +279,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
@ -208,6 +295,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,
@ -233,7 +322,44 @@ 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,
@ -546,12 +672,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