summarylogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarc Mettke2017-02-03 12:02:06 +0100
committerMarc Mettke2017-02-03 12:12:31 +0100
commitbd45f984bc291047104f4e92013ceec067dc70d5 (patch)
tree2e7edd24cfb94b4fa9e2b01e7f705e1b2b1b3eb3
downloadaur-bd45f984bc291047104f4e92013ceec067dc70d5.tar.gz
[gitlab-runner-custom-executors] 1.10.4-1
-rw-r--r--.SRCINFO45
-rw-r--r--.gitignore11
-rw-r--r--PKGBUILD81
-rw-r--r--config.toml1
-rw-r--r--gitlab-runner.install8
-rw-r--r--gitlab-runner.service17
-rw-r--r--gitlab-runner.sysusers1
-rw-r--r--gitlab-runner.tmpfiles1
-rw-r--r--lxc_executor.patch499
9 files changed, 664 insertions, 0 deletions
diff --git a/.SRCINFO b/.SRCINFO
new file mode 100644
index 000000000000..31a97681ff69
--- /dev/null
+++ b/.SRCINFO
@@ -0,0 +1,45 @@
+pkgbase = gitlab-runner-custom-executors
+ pkgdesc = The official GitLab CI runner written in Go with a LXC Executor
+ pkgver = 1.10.4
+ pkgrel = 1
+ url = https://gitlab.com/gitlab-org/gitlab-ci-multi-runner
+ install = gitlab-runner.install
+ arch = i686
+ arch = x86_64
+ license = GPL3
+ makedepends = git
+ makedepends = go
+ makedepends = git
+ makedepends = go-bindata
+ makedepends = mercurial
+ depends = ca-certificates
+ depends = curl
+ depends = git
+ depends = glibc
+ depends = tar
+ provides = gitlab-runner=1.10.4-1
+ conflicts = gitlab-runner
+ noextract = prebuilt-x86_64.tar.xz
+ noextract = prebuilt-arm.tar.xz
+ backup = etc/gitlab-runner/config.toml
+ source = git+https://gitlab.com/gitlab-org/gitlab-ci-multi-runner.git#tag=v1.10.4
+ source = https://gitlab-ci-multi-runner-downloads.s3.amazonaws.com/master/docker/prebuilt-x86_64.tar.xz
+ source = https://gitlab-ci-multi-runner-downloads.s3.amazonaws.com/master/docker/prebuilt-arm.tar.xz
+ source = gitlab-runner.install
+ source = gitlab-runner.service
+ source = gitlab-runner.sysusers
+ source = gitlab-runner.tmpfiles
+ source = config.toml
+ source = lxc_executor.patch
+ sha512sums = SKIP
+ sha512sums = SKIP
+ sha512sums = SKIP
+ sha512sums = d5888f378c5b12b84e4238b191ef56a97a81c9073d27dcb47065998f0a1f6caf9f13314ae908e72a06f4d29d1bd1f4f72338c97268391e2c98706216c8281f3e
+ sha512sums = ed24841242a56a3b10dd80cd23e0c980f6bbe5fd0ebd4c6b46529947e4920cc9c03e4f4b239da8a798c801a6cdd757415113f97e45c1032f2c519fdaec4d3ae0
+ sha512sums = 8aa7f08702e99053c696fcc2aaba83beb9e9cd6f31973d82862db9350ac46df3a095377625d31fe909677525290d2de922d7a97930ed235774cb8f0da8944d40
+ sha512sums = 6751d9fa0b27172d1b419c4138f5ac15cbc7c9147653a7258cf1470216142c637210bb60608c7ed0974e0e4057e5ddeae32225df1bb36e7dd1f20fec71e33cc3
+ sha512sums = f39c23fc06636f31c3fadb9a630c54527e8255098f18d275772cb30875d0a7463717101704070d432f2b69ab71f076a9538172a439bc307722dad2c7e260f752
+ sha512sums = f88b8e6a8afb4f11bf61cfb6f680f611946658a8ea9c31588fb0b4be505bc72ac1af30ce819a2e74631d47919d684f121a625c091b3e6e891df5c434ae04acff
+
+pkgname = gitlab-runner-custom-executors
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000000..a9eb70d24404
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+*
+!config.toml
+!.git
+!.gitignore
+!gitlab-runner.install
+!gitlab-runner.service
+!gitlab-runner.sysusers
+!gitlab-runner.tmpfiles
+!lxc_executor.patch
+!PKGBUILD
+!.SRCINFO
diff --git a/PKGBUILD b/PKGBUILD
new file mode 100644
index 000000000000..a39608939d94
--- /dev/null
+++ b/PKGBUILD
@@ -0,0 +1,81 @@
+# Maintainer: Sven-Hendrik Haase <sh@lutzhaase.com>
+# Contributor: Lubomir 'Kuci' Kucera <kuci24-at-gmail-dot-com>
+# Custom Executor Maintainer: Marc Mettke <marc@itmettke.de>
+
+pkgname=gitlab-runner-custom-executors
+pkgver=1.10.4
+pkgrel=1
+pkgdesc="The official GitLab CI runner written in Go with a LXC Executor"
+arch=('i686' 'x86_64')
+url='https://gitlab.com/gitlab-org/gitlab-ci-multi-runner'
+license=('GPL3')
+depends=('ca-certificates' 'curl' 'git' 'glibc' 'tar')
+makedepends=('git' 'go' 'git' 'go-bindata' 'mercurial')
+install='gitlab-runner.install'
+backup=('etc/gitlab-runner/config.toml')
+noextract=('prebuilt-x86_64.tar.xz'
+ 'prebuilt-arm.tar.xz')
+provides=("${pkgname/-custom-executors}=${pkgver}-${pkgrel}")
+conflicts=("${pkgname/-custom-executors}")
+
+# Note: This should be built using git because the runner gets its version information from there and I
+# haven't found the place to patch that yet.
+source=("git+https://gitlab.com/gitlab-org/gitlab-ci-multi-runner.git#tag=v${pkgver}"
+ "https://gitlab-ci-multi-runner-downloads.s3.amazonaws.com/master/docker/prebuilt-x86_64.tar.xz"
+ "https://gitlab-ci-multi-runner-downloads.s3.amazonaws.com/master/docker/prebuilt-arm.tar.xz"
+ "gitlab-runner.install"
+ "gitlab-runner.service"
+ "gitlab-runner.sysusers"
+ "gitlab-runner.tmpfiles"
+ "config.toml"
+ "lxc_executor.patch")
+sha512sums=('SKIP'
+ 'SKIP'
+ 'SKIP'
+ 'd5888f378c5b12b84e4238b191ef56a97a81c9073d27dcb47065998f0a1f6caf9f13314ae908e72a06f4d29d1bd1f4f72338c97268391e2c98706216c8281f3e'
+ 'ed24841242a56a3b10dd80cd23e0c980f6bbe5fd0ebd4c6b46529947e4920cc9c03e4f4b239da8a798c801a6cdd757415113f97e45c1032f2c519fdaec4d3ae0'
+ '8aa7f08702e99053c696fcc2aaba83beb9e9cd6f31973d82862db9350ac46df3a095377625d31fe909677525290d2de922d7a97930ed235774cb8f0da8944d40'
+ '6751d9fa0b27172d1b419c4138f5ac15cbc7c9147653a7258cf1470216142c637210bb60608c7ed0974e0e4057e5ddeae32225df1bb36e7dd1f20fec71e33cc3'
+ 'f39c23fc06636f31c3fadb9a630c54527e8255098f18d275772cb30875d0a7463717101704070d432f2b69ab71f076a9538172a439bc307722dad2c7e260f752'
+ 'f88b8e6a8afb4f11bf61cfb6f680f611946658a8ea9c31588fb0b4be505bc72ac1af30ce819a2e74631d47919d684f121a625c091b3e6e891df5c434ae04acff')
+
+prepare() {
+ mkdir -p "${srcdir}/src/gitlab.com/gitlab-org/"
+ ln -sf "${srcdir}/gitlab-ci-multi-runner" "${srcdir}/src/gitlab.com/gitlab-org/gitlab-ci-multi-runner"
+ cd "${srcdir}/src/gitlab.com/gitlab-org/gitlab-ci-multi-runner"
+
+ msg "Patch add lxc Executor"
+ patch -Np1 -i "${srcdir}/lxc_executor.patch"
+
+ export GOPATH="${srcdir}"
+ make deps
+
+ ln -sf "${srcdir}/prebuilt-x86_64.tar.xz" prebuilt-x86_64.tar.xz
+ ln -sf "${srcdir}/prebuilt-x86_64.tar.xz" prebuilt-arm.tar.xz
+}
+
+build() {
+ cd "${srcdir}/src/gitlab.com/gitlab-org/gitlab-ci-multi-runner"
+
+ GOPATH="${srcdir}" go-bindata \
+ -pkg docker \
+ -nocompress \
+ -nomemcopy \
+ -prefix out/docker/ \
+ -o executors/docker/bindata.go \
+ prebuilt-x86_64.tar.xz \
+ prebuilt-arm.tar.xz
+
+ GOPATH="${srcdir}" go build
+}
+
+package() {
+ cd "${srcdir}/src/gitlab.com/gitlab-org/gitlab-ci-multi-runner"
+
+ install -Dm644 "${srcdir}/config.toml" "${pkgdir}/etc/gitlab-runner/config.toml"
+ install -Dm644 "${srcdir}/gitlab-runner.service" "${pkgdir}/usr/lib/systemd/system/gitlab-runner.service"
+ install -Dm644 "${srcdir}/gitlab-runner.sysusers" "${pkgdir}/usr/lib/sysusers.d/gitlab-runner.conf"
+ install -Dm644 "${srcdir}/gitlab-runner.tmpfiles" "${pkgdir}/usr/lib/tmpfiles.d/gitlab-runner.conf"
+ install -Dm755 "gitlab-ci-multi-runner" "${pkgdir}/usr/bin/gitlab-ci-multi-runner"
+ ln -s /usr/bin/gitlab-ci-multi-runner "${pkgdir}/usr/bin/gitlab-runner"
+}
diff --git a/config.toml b/config.toml
new file mode 100644
index 000000000000..97e906ccde9d
--- /dev/null
+++ b/config.toml
@@ -0,0 +1 @@
+concurrent = 4
diff --git a/gitlab-runner.install b/gitlab-runner.install
new file mode 100644
index 000000000000..28676f89116a
--- /dev/null
+++ b/gitlab-runner.install
@@ -0,0 +1,8 @@
+post_install() {
+ systemd-sysusers gitlab-runner.conf
+ systemd-tmpfiles --create gitlab-runner.conf
+ echo "Register the runner as root using"
+ echo "# gitlab-ci-multi-runner register"
+ echo "Configure the runner in /etc/gitlab-runner/config.toml"
+ echo "Use gitlab-runner.service to control the runner"
+}
diff --git a/gitlab-runner.service b/gitlab-runner.service
new file mode 100644
index 000000000000..77ea3c78cdef
--- /dev/null
+++ b/gitlab-runner.service
@@ -0,0 +1,17 @@
+[Unit]
+Description=GitLab Runner
+After=syslog.target network.target
+ConditionFileIsExecutable=/usr/bin/gitlab-ci-multi-runner
+
+[Service]
+StartLimitInterval=5
+StartLimitBurst=10
+ExecStart=/usr/bin/gitlab-ci-multi-runner "run" "--working-directory" "/var/lib/gitlab-runner" "--config" "/etc/gitlab-runner/config.toml" "--service" "gitlab-runner" "--user" "gitlab-runner"
+Restart=always
+RestartSec=120
+StandardOutput=syslog
+StandardError=syslog
+SyslogIdentifier=gitlab-runner
+
+[Install]
+WantedBy=multi-user.target
diff --git a/gitlab-runner.sysusers b/gitlab-runner.sysusers
new file mode 100644
index 000000000000..5ceb7a64df7c
--- /dev/null
+++ b/gitlab-runner.sysusers
@@ -0,0 +1 @@
+u gitlab-runner 107 "GitLab Runner" /var/lib/gitlab-runner
diff --git a/gitlab-runner.tmpfiles b/gitlab-runner.tmpfiles
new file mode 100644
index 000000000000..73a9916a6277
--- /dev/null
+++ b/gitlab-runner.tmpfiles
@@ -0,0 +1 @@
+d /var/lib/gitlab-runner 0700 gitlab-runner gitlab-runner -
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
+