[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 c2eaf440f5
)
Conflicts:
pkg/container/host_environment.go
Conflicts:
pkg/container/host_environment.go
[FORGJEO] upgrade to node20
This commit is contained in:
parent
b9009a7a20
commit
d5915243ad
4 changed files with 183 additions and 10 deletions
|
@ -5,6 +5,8 @@ import "context"
|
||||||
type ExecutionsEnvironment interface {
|
type ExecutionsEnvironment interface {
|
||||||
Container
|
Container
|
||||||
ToContainerPath(string) string
|
ToContainerPath(string) string
|
||||||
|
GetName() string
|
||||||
|
GetRoot() string
|
||||||
GetActPath() string
|
GetActPath() string
|
||||||
GetPathVariableName() string
|
GetPathVariableName() string
|
||||||
DefaultPathVariable() string
|
DefaultPathVariable() string
|
||||||
|
|
|
@ -26,16 +26,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type HostEnvironment struct {
|
type HostEnvironment struct {
|
||||||
|
Name string
|
||||||
Path string
|
Path string
|
||||||
TmpDir string
|
TmpDir string
|
||||||
ToolCache string
|
ToolCache string
|
||||||
Workdir string
|
Workdir string
|
||||||
ActPath string
|
ActPath string
|
||||||
|
Root string
|
||||||
CleanUp func()
|
CleanUp func()
|
||||||
StdOut io.Writer
|
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 func(ctx context.Context) error {
|
||||||
return nil
|
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 {
|
return func(ctx context.Context) error {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
srcPrefix := filepath.Dir(srcPath)
|
srcPrefix := filepath.Dir(srcPath)
|
||||||
|
@ -288,7 +290,7 @@ func getEnvListFromMap(env map[string]string) []string {
|
||||||
return envList
|
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)
|
envList := getEnvListFromMap(env)
|
||||||
var wd string
|
var wd string
|
||||||
if workdir != "" {
|
if workdir != "" {
|
||||||
|
@ -300,6 +302,19 @@ func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline st
|
||||||
} else {
|
} else {
|
||||||
wd = e.Path
|
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)
|
f, err := lookupPathHost(command[0], env, e.StdOut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -342,7 +357,7 @@ func (e *HostEnvironment) exec(ctx context.Context, command []string, cmdline st
|
||||||
}
|
}
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("RUN %w", err)
|
||||||
}
|
}
|
||||||
if tty != nil {
|
if tty != nil {
|
||||||
writer.AutoStop = true
|
writer.AutoStop = true
|
||||||
|
@ -399,6 +414,14 @@ func (e *HostEnvironment) ToContainerPath(path string) string {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *HostEnvironment) GetName() string {
|
||||||
|
return e.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HostEnvironment) GetRoot() string {
|
||||||
|
return e.Root
|
||||||
|
}
|
||||||
|
|
||||||
func (e *HostEnvironment) GetActPath() string {
|
func (e *HostEnvironment) GetActPath() string {
|
||||||
actPath := e.ActPath
|
actPath := e.ActPath
|
||||||
if runtime.GOOS == "windows" {
|
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
|
org := e.StdOut
|
||||||
e.StdOut = stdout
|
e.StdOut = stdout
|
||||||
return org, org
|
return org, org
|
||||||
|
|
|
@ -10,8 +10,7 @@ import (
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LinuxContainerEnvironmentExtensions struct {
|
type LinuxContainerEnvironmentExtensions struct{}
|
||||||
}
|
|
||||||
|
|
||||||
// Resolves the equivalent host path inside the container
|
// 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
|
// 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
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*LinuxContainerEnvironmentExtensions) GetName() string {
|
||||||
|
return "NAME"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*LinuxContainerEnvironmentExtensions) GetRoot() string {
|
||||||
|
return "/var/run"
|
||||||
|
}
|
||||||
|
|
||||||
func (*LinuxContainerEnvironmentExtensions) GetActPath() string {
|
func (*LinuxContainerEnvironmentExtensions) GetActPath() string {
|
||||||
return "/var/run/act"
|
return "/var/run/act"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,11 @@ package runner
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
|
_ "embed"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
@ -16,6 +18,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
"github.com/nektos/act/pkg/common"
|
"github.com/nektos/act/pkg/common"
|
||||||
|
@ -183,6 +186,94 @@ func (rc *RunContext) GetBindsAndMounts() ([]string, map[string]string) {
|
||||||
return binds, mounts
|
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 {
|
func (rc *RunContext) startHostEnvironment() common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
logger := common.Logger(ctx)
|
logger := common.Logger(ctx)
|
||||||
|
@ -198,7 +289,8 @@ func (rc *RunContext) startHostEnvironment() common.Executor {
|
||||||
cacheDir := rc.ActionCacheDir()
|
cacheDir := rc.ActionCacheDir()
|
||||||
randBytes := make([]byte, 8)
|
randBytes := make([]byte, 8)
|
||||||
_, _ = rand.Read(randBytes)
|
_, _ = rand.Read(randBytes)
|
||||||
miscpath := filepath.Join(cacheDir, hex.EncodeToString(randBytes))
|
randName := hex.EncodeToString(randBytes)
|
||||||
|
miscpath := filepath.Join(cacheDir, randName)
|
||||||
actPath := filepath.Join(miscpath, "act")
|
actPath := filepath.Join(miscpath, "act")
|
||||||
if err := os.MkdirAll(actPath, 0o777); err != nil {
|
if err := os.MkdirAll(actPath, 0o777); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -213,6 +305,8 @@ func (rc *RunContext) startHostEnvironment() common.Executor {
|
||||||
}
|
}
|
||||||
toolCache := filepath.Join(cacheDir, "tool_cache")
|
toolCache := filepath.Join(cacheDir, "tool_cache")
|
||||||
rc.JobContainer = &container.HostEnvironment{
|
rc.JobContainer = &container.HostEnvironment{
|
||||||
|
Name: randName,
|
||||||
|
Root: miscpath,
|
||||||
Path: path,
|
Path: path,
|
||||||
TmpDir: runnerTmp,
|
TmpDir: runnerTmp,
|
||||||
ToolCache: toolCache,
|
ToolCache: toolCache,
|
||||||
|
@ -238,7 +332,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(
|
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{
|
rc.JobContainer.Copy(rc.JobContainer.GetActPath()+"/", &container.FileEntry{
|
||||||
Name: "workflow/event.json",
|
Name: "workflow/event.json",
|
||||||
Mode: 0o644,
|
Mode: 0o644,
|
||||||
|
@ -601,12 +732,22 @@ func (rc *RunContext) IsHostEnv(ctx context.Context) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RunContext) stopContainer() common.Executor {
|
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 {
|
func (rc *RunContext) closeContainer() common.Executor {
|
||||||
return func(ctx context.Context) error {
|
return func(ctx context.Context) error {
|
||||||
if rc.JobContainer != nil {
|
if rc.JobContainer != nil {
|
||||||
|
image := rc.platformImage(ctx)
|
||||||
|
if strings.EqualFold(image, "-self-hosted") {
|
||||||
|
return rc.stopHostEnvironment()(ctx)
|
||||||
|
}
|
||||||
return rc.JobContainer.Close()(ctx)
|
return rc.JobContainer.Close()(ctx)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -628,7 +769,7 @@ func (rc *RunContext) steps() []*model.Step {
|
||||||
// Executor returns a pipeline executor for all the steps in the job
|
// Executor returns a pipeline executor for all the steps in the job
|
||||||
func (rc *RunContext) Executor() (common.Executor, error) {
|
func (rc *RunContext) Executor() (common.Executor, error) {
|
||||||
var executor common.Executor
|
var executor common.Executor
|
||||||
var jobType, err = rc.Run.Job().Type()
|
jobType, err := rc.Run.Job().Type()
|
||||||
|
|
||||||
switch jobType {
|
switch jobType {
|
||||||
case model.JobTypeDefault:
|
case model.JobTypeDefault:
|
||||||
|
|
Loading…
Reference in a new issue