summarylogtreecommitdiffstats
path: root/lxc_executor.patch
diff options
context:
space:
mode:
Diffstat (limited to 'lxc_executor.patch')
-rw-r--r--lxc_executor.patch499
1 files changed, 499 insertions, 0 deletions
diff --git a/lxc_executor.patch b/lxc_executor.patch
new file mode 100644
index 000000000000..9f4727e0d88e
--- /dev/null
+++ b/lxc_executor.patch
@@ -0,0 +1,499 @@
+From c9008c8525a3daacd8d21d129e98e103134615bb Mon Sep 17 00:00:00 2001
+From: Marc Mettke <marc@itmettke.de>
+Date: Sun, 8 Jan 2017 20:50:32 +0100
+Subject: [PATCH] Implemented LXC as Executor
+
+* Container defined by "container-name"
+* Detects whether a Container is started or not
+* Container is started if currently not running
+* SubContainers can be created with OverlayFS using "slave-name"
+* Multiple SubContainers can be used by appending a "randomid"
+* Added Exec Command
+* Added Register Command
+---
+ commands/exec.go | 1 +
+ commands/register.go | 26 ++++++++++++++++++++++++++
+ common/config.go | 7 +++++++
+ executors/lxc/executor_lxc.go | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ executors/lxc/executor_lxc_test.go | 1 +
+ helpers/lxc/lxc.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ helpers/lxc/lxc_command.go | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ main.go | 1 +
+ 8 files changed, 380 insertions(+), 0 deletions(-)
+ create mode 100644 executors/lxc/executor_lxc.go
+ create mode 100644 executors/lxc/executor_lxc_test.go
+ create mode 100644 helpers/lxc/lxc.go
+ create mode 100644 helpers/lxc/lxc_command.go
+
+diff --git a/commands/exec.go b/commands/exec.go
+index 5ae5491..2228d79 100644
+--- a/commands/exec.go
++++ b/commands/exec.go
+@@ -16,6 +16,7 @@ import (
+
+ // Force to load all executors, executes init() on them
+ _ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors/docker"
++ _ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors/lxc"
+ _ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors/parallels"
+ _ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors/shell"
+ _ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors/ssh"
+diff --git a/commands/register.go b/commands/register.go
+index ac1539e..a3eaec2 100644
+--- a/commands/register.go
++++ b/commands/register.go
+@@ -107,6 +107,19 @@ func (s *RegisterCommand) askDocker() {
+ s.Docker.Volumes = append(s.Docker.Volumes, "/cache")
+ }
+
++func (s *RegisterCommand) askLxc() {
++ s.LXC.ContainerName = s.ask("lxc-container-name", "Please enter the Name of the Container (e.g. my-container)")
++ s.LXC.SlaveName = s.ask("lxc-slave-name", "Please enter a Name for a Slave Container to be created by the runner if you don't want to persist changes in the container (e.g. my-slave-container)")
++ if s.LXC.SlaveName != "" {
++ var err error
++ answer := s.ask("lxc-container-name", "Please enter true if you want to use Random Ids for your Slave Container to use multiple at once (e.g. true)")
++ s.LXC.SlaveRandomIdentifier, err = strconv.ParseBool(answer)
++ if err != nil {
++ s.LXC.SlaveRandomIdentifier = false
++ }
++ }
++}
++
+ func (s *RegisterCommand) askParallels() {
+ s.Parallels.BaseName = s.ask("parallels-vm", "Please enter the Parallels VM (e.g. my-vm):")
+ }
+@@ -217,6 +230,18 @@ func (s *RegisterCommand) askExecutorOptions() {
+ s.askVirtualBox()
+ s.askSSHLogin()
+ }
++ additionalExecutors(s)
++}
++
++func additionalExecutors(s *RegisterCommand) {
++ lxc := s.LXC
++ s.LXC = nil
++
++ switch s.Executor {
++ case "lxc":
++ s.LXC = lxc
++ s.askLxc()
++ }
+ }
+
+ func (s *RegisterCommand) Execute(context *cli.Context) {
+@@ -279,6 +304,7 @@ func init() {
+ Machine: &common.DockerMachine{},
+ Docker: &common.DockerConfig{},
+ SSH: &ssh.Config{},
++ LXC: &common.LxcConfig{},
+ Parallels: &common.ParallelsConfig{},
+ VirtualBox: &common.VirtualBoxConfig{},
+ },
+diff --git a/common/config.go b/common/config.go
+index 463a4bc..290b634 100644
+--- a/common/config.go
++++ b/common/config.go
+@@ -87,6 +87,12 @@ type DockerMachine struct {
+ offPeakTimePeriods *timeperiod.TimePeriod
+ }
+
++type LxcConfig struct {
++ ContainerName string `toml:"container-name,omitempty" json:"container-name" long:"container-name" env:"LXC_CONTAINER_NAME" description:"The Name of the Container to use"`
++ SlaveName string `toml:"slave-name,omitempty" json:"slave-name" long:"slave-name" env:"LXC_SLAVE_NAME" description:"The Name of the Slave to create from the Container (deleted after each build)"`
++ SlaveRandomIdentifier bool `toml:"slave-random-id,omitempty" json:"slave-random-id" long:"slave-random-identifier" env:"LXC_RANDOM_ID" description:"Adds a Random Id to the Slave at creation to allow a Runner to use multiple Machines at once"`
++}
++
+ type ParallelsConfig struct {
+ BaseName string `toml:"base_name" json:"base_name" long:"base-name" env:"PARALLELS_BASE_NAME" description:"VM name to be used"`
+ TemplateName string `toml:"template_name,omitempty" json:"template_name" long:"template-name" env:"PARALLELS_TEMPLATE_NAME" description:"VM template to be created"`
+@@ -172,6 +178,7 @@ type RunnerSettings struct {
+ Cache *CacheConfig `toml:"cache,omitempty" json:"cache" group:"cache configuration" namespace:"cache"`
+ Machine *DockerMachine `toml:"machine,omitempty" json:"machine" group:"docker machine provider" namespace:"machine"`
+ Kubernetes *KubernetesConfig `toml:"kubernetes,omitempty" json:"kubernetes" group:"kubernetes executor" namespace:"kubernetes"`
++ LXC *LxcConfig `toml:"lxc,omitempty" json:"lxc" group:"lxc executor" namespace:"lxc"`
+ }
+
+ type RunnerConfig struct {
+diff --git a/executors/lxc/executor_lxc.go b/executors/lxc/executor_lxc.go
+new file mode 100644
+index 0000000..f043efb
+--- /dev/null
++++ b/executors/lxc/executor_lxc.go
+@@ -0,0 +1,92 @@
++package lxc
++
++import (
++ "errors"
++
++ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/common"
++ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors"
++ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers/lxc"
++)
++
++type executor struct {
++ executors.AbstractExecutor
++ command lxc.Client
++}
++
++func (e *executor) Prepare(globalConfig *common.Config, config *common.RunnerConfig, build *common.Build) error {
++ err := e.AbstractExecutor.Prepare(globalConfig, config, build)
++ if err != nil {
++ return err
++ }
++
++ e.Println("Using LXC executor...")
++ if e.BuildShell.PassFile {
++ return errors.New("LXC doesn't support shells that require script file")
++ }
++
++ if e.Config.LXC == nil {
++ return errors.New("Missing LXC configuration")
++ }
++
++ e.Debugln("Starting LXC command...")
++
++ // Create LXC command
++ e.command = lxc.Client{
++ Config: *e.Config.LXC,
++ Stdout: e.BuildTrace,
++ Stderr: e.BuildTrace,
++ }
++
++ e.Debugln("Connecting to LXC server...")
++ err = e.command.Connect()
++ if err != nil {
++ return err
++ }
++ return nil
++}
++
++func (e *executor) Run(cmd common.ExecutorCommand) error {
++ err := e.command.Run(lxc.Command{
++ Environment: e.BuildShell.Environment,
++ Command: e.BuildShell.GetCommandWithArguments(),
++ Stdin: cmd.Script,
++ Abort: cmd.Abort,
++ })
++ return err
++}
++
++func (e *executor) Cleanup() {
++ e.command.Cleanup()
++ e.AbstractExecutor.Cleanup()
++}
++
++func init() {
++ options := executors.ExecutorOptions{
++ DefaultBuildsDir: "builds",
++ DefaultCacheDir: "/cache",
++ SharedBuildsDir: true,
++ Shell: common.ShellScriptInfo{
++ Shell: "bash",
++ Type: common.LoginShell,
++ RunnerCommand: "gitlab-runner",
++ },
++ ShowHostname: true,
++ }
++
++ creator := func() common.Executor {
++ return &executor{
++ AbstractExecutor: executors.AbstractExecutor{
++ ExecutorOptions: options,
++ },
++ }
++ }
++
++ featuresUpdater := func(features *common.FeaturesInfo) {
++ features.Variables = true
++ }
++
++ common.RegisterExecutor("lxc", executors.DefaultExecutorProvider{
++ Creator: creator,
++ FeaturesUpdater: featuresUpdater,
++ })
++}
+diff --git a/executors/lxc/executor_lxc_test.go b/executors/lxc/executor_lxc_test.go
+new file mode 100644
+index 0000000..99b5cd3
+--- /dev/null
++++ b/executors/lxc/executor_lxc_test.go
+@@ -0,0 +1 @@
++package lxc_test
+diff --git a/helpers/lxc/lxc.go b/helpers/lxc/lxc.go
+new file mode 100644
+index 0000000..1ca0f94
+--- /dev/null
++++ b/helpers/lxc/lxc.go
+@@ -0,0 +1,81 @@
++package lxc
++
++import (
++ "io"
++ "math/rand"
++ "os/exec"
++ "strconv"
++ "strings"
++ "time"
++)
++
++const (
++ RUNNING = 1
++ STOPPED = 2
++ UNDEFINED = 3
++)
++
++func initializeRandomSeed() {
++ rand.Seed(time.Now().UTC().UnixNano())
++}
++
++func getRandomNumber(min int64, max int64) int64 {
++ return min + rand.Int63n(max-min)
++}
++
++func getRandomNameForContainer(name string) string {
++ return name + strconv.FormatInt(getRandomNumber(100000000, 999999999), 10)
++}
++
++func checkContainerState(name string, stderr io.Writer) (int, error) {
++ cmd := exec.Command("lxc-info", "-sHn", name)
++ cmd.Stderr = stderr
++ out, err := cmd.Output()
++ if err != nil {
++ return -1, err
++ }
++ output := string(out)
++ if strings.Contains(output, "RUNNING") {
++ return RUNNING, nil
++ } else if strings.Contains(output, "STOPPED") {
++ return STOPPED, nil
++ } else {
++ return UNDEFINED, nil
++ }
++}
++
++func startContainer(name string, stdout io.Writer, stderr io.Writer) error {
++ cmd := exec.Command("lxc-start", "-n", name)
++ cmd.Stdout = stdout
++ cmd.Stderr = stderr
++ return cmd.Run()
++}
++
++func attachToContainer(name string, stdout io.Writer, stderr io.Writer, commands ...string) *exec.Cmd {
++ arguments := append([]string{"-n", name, "--"}, commands...)
++ cmd := exec.Command("lxc-attach", arguments...)
++ cmd.Stdout = stdout
++ cmd.Stderr = stderr
++ return cmd
++}
++
++func copyContainerAsSnapshot(name string, newname string, stdout io.Writer, stderr io.Writer) error {
++ cmd := exec.Command("lxc-copy", "-sn", name, "-N", newname)
++ cmd.Stdout = stdout
++ cmd.Stderr = stderr
++ return cmd.Run()
++}
++
++func stopContainer(name string, stdout io.Writer, stderr io.Writer) error {
++ cmd := exec.Command("lxc-stop", "-n", name)
++ cmd.Stdout = stdout
++ cmd.Stderr = stderr
++ return cmd.Run()
++}
++
++func destroyContainer(name string, stdout io.Writer, stderr io.Writer) error {
++ cmd := exec.Command("lxc-destroy", "-n", name)
++ cmd.Stdout = stdout
++ cmd.Stderr = stderr
++ return cmd.Run()
++}
+diff --git a/helpers/lxc/lxc_command.go b/helpers/lxc/lxc_command.go
+new file mode 100644
+index 0000000..76fc1c2
+--- /dev/null
++++ b/helpers/lxc/lxc_command.go
+@@ -0,0 +1,171 @@
++package lxc
++
++import (
++ "bytes"
++ "errors"
++ "io"
++ "time"
++
++ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/common"
++ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/helpers"
++)
++
++type Client struct {
++ Config common.LxcConfig
++
++ Stdout io.Writer
++ Stderr io.Writer
++ ConnectRetries int
++
++ connected bool
++ stop bool
++ destroy bool
++ container string
++}
++
++type Command struct {
++ Environment []string
++ Command []string
++ Stdin string
++ Abort chan interface{}
++}
++
++type ExitError struct {
++ Inner error
++}
++
++func (e *ExitError) Error() string {
++ if e.Inner == nil {
++ return "error"
++ }
++ return e.Inner.Error()
++}
++
++func handleSlaveCreation(c *Client) error {
++ if c.Config.SlaveName != "" {
++ c.stop = true
++ c.destroy = true
++ c.container = c.Config.SlaveName
++ if c.Config.SlaveRandomIdentifier == true {
++ c.container = getRandomNameForContainer(c.container)
++ }
++ return copyContainerAsSnapshot(c.Config.ContainerName, c.container, c.Stdout, c.Stderr)
++ }
++ return nil
++}
++
++func handleStarted(c *Client) error {
++ if c.Config.SlaveName != "" {
++ return errors.New("The Container " + c.Config.ContainerName + " must not be used when defining OverlayFSName. Please stop it and try again")
++ }
++ return nil
++}
++
++func handleStopped(c *Client) error {
++ err := handleSlaveCreation(c)
++ if err != nil {
++ return err
++ }
++ err = startContainer(c.container, c.Stdout, c.Stderr)
++ if err != nil {
++ return err
++ }
++ state, err := checkContainerState(c.container, c.Stderr)
++ if err != nil {
++ return err
++ }
++ if state != RUNNING {
++ return errors.New("Unable to start Container")
++ }
++ return nil
++}
++
++func waitForNetwork() {
++ time.Sleep(10 * time.Second)
++}
++
++func (c *Client) Connect() error {
++ c.stop = false
++ c.destroy = false
++ if c.Config.ContainerName == "" {
++ return errors.New("Host cannot be empty")
++ }
++ if c.Config.SlaveRandomIdentifier == true && c.Config.SlaveName == "" {
++ c.Stdout.Write([]byte("Warning: RandomIndentifier: true can only be used with OverlayFSName and will be ignored"))
++ }
++ initializeRandomSeed()
++ state, err := checkContainerState(c.Config.ContainerName, c.Stderr)
++ if err != nil {
++ return err
++ }
++ if state == STOPPED {
++ handleStopped(c)
++ } else if state == RUNNING {
++ handleStarted(c)
++ } else {
++ return errors.New("Could not determinate Container State")
++ }
++ waitForNetwork()
++ c.connected = true
++ return nil
++}
++
++func (c *Client) Exec(command string) error {
++ if c.connected != true {
++ return errors.New("Container not accessible")
++ }
++ return attachToContainer(c.container, c.Stdout, c.Stderr, command).Run()
++}
++
++func (c *Command) fullCommand() []string {
++ var arguments []string
++ // TODO: This method is compatible only with Bjourne compatible shells
++ for _, part := range c.Command {
++ arguments = append(arguments, part)
++ }
++ return arguments
++}
++
++func (c *Client) Run(command Command) error {
++ if c.connected != true {
++ return errors.New("Container not accessible")
++ }
++
++ var envVariables bytes.Buffer
++ for _, keyValue := range command.Environment {
++ envVariables.WriteString("export " + helpers.ShellEscape(keyValue) + "\n")
++ }
++
++ cmd := attachToContainer(c.container, c.Stdout, c.Stderr, command.fullCommand()...)
++ cmd.Stdin = io.MultiReader(
++ &envVariables,
++ bytes.NewBufferString(command.Stdin),
++ )
++ err := cmd.Start()
++ if err != nil {
++ return err
++ }
++
++ waitCh := make(chan error)
++ go func() {
++ waitCh <- cmd.Wait()
++ }()
++
++ select {
++ case <-command.Abort:
++ cmd.Process.Kill()
++ return <-waitCh
++
++ case err := <-waitCh:
++ return err
++ }
++}
++
++func (c *Client) Cleanup() {
++ if c.stop {
++ stopContainer(c.container, c.Stdout, c.Stderr)
++ }
++ if c.destroy {
++ destroyContainer(c.container, c.Stdout, c.Stderr)
++ }
++}
+diff --git a/main.go b/main.go
+index 6a15969..71ee0e9 100644
+--- a/main.go
++++ b/main.go
+@@ -15,6 +15,7 @@ import (
+ _ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors/docker"
+ _ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors/docker/machine"
+ _ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors/kubernetes"
++ _ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors/lxc"
+ _ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors/parallels"
+ _ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors/shell"
+ _ "gitlab.com/gitlab-org/gitlab-ci-multi-runner/executors/ssh"
+--
+libgit2 0.24.0
+