From 1f2135f65edca7413fc02615c677a27f1ff887af Mon Sep 17 00:00:00 2001 From: Matt Schreiber Date: Mon, 30 Oct 2017 21:48:43 -0400 Subject: [PATCH] Add options to LXC builder for influencing for how containers are built and started via - create_options: a list of options passed to lxc-create - start_options: a list of options passed to lxc-start - attach_options: a list of options passed to lxc-attach Also extended existing LXC builder BATS tests to exercise the new builder options, and added website docs. --- builder/lxc/communicator.go | 8 +- builder/lxc/config.go | 3 + builder/lxc/step_lxc_create.go | 7 +- builder/lxc/step_provision.go | 1 + builder/lxc/step_wait_init.go | 1 + test/builder_lxc.bats | 98 ++++++++++++++++++++---- test/fixtures/builder-lxc/minimal.json | 18 ++++- website/source/docs/builders/lxc.html.md | 15 ++++ 8 files changed, 131 insertions(+), 20 deletions(-) diff --git a/builder/lxc/communicator.go b/builder/lxc/communicator.go index 8d9765979..6e41ace60 100644 --- a/builder/lxc/communicator.go +++ b/builder/lxc/communicator.go @@ -16,6 +16,7 @@ import ( type LxcAttachCommunicator struct { RootFs string ContainerName string + AttachOptions []string CmdWrapper CommandWrapper } @@ -110,8 +111,13 @@ func (c *LxcAttachCommunicator) DownloadDir(src string, dst string, exclude []st func (c *LxcAttachCommunicator) Execute(commandString string) (*exec.Cmd, error) { log.Printf("Executing with lxc-attach in container: %s %s %s", c.ContainerName, c.RootFs, commandString) + + attachCommand := []string{"sudo", "lxc-attach"} + attachCommand = append(attachCommand, c.AttachOptions...) + attachCommand = append(attachCommand, []string{"--name", "%s", "--", "/bin/sh -c \"%s\""}...) + command, err := c.CmdWrapper( - fmt.Sprintf("sudo lxc-attach --name %s -- /bin/sh -c \"%s\"", c.ContainerName, commandString)) + fmt.Sprintf(strings.Join(attachCommand, " "), c.ContainerName, commandString)) if err != nil { return nil, err } diff --git a/builder/lxc/config.go b/builder/lxc/config.go index c3c28d4fb..5d49dbfd6 100644 --- a/builder/lxc/config.go +++ b/builder/lxc/config.go @@ -18,6 +18,9 @@ type Config struct { ContainerName string `mapstructure:"container_name"` CommandWrapper string `mapstructure:"command_wrapper"` RawInitTimeout string `mapstructure:"init_timeout"` + CreateOptions []string `mapstructure:"create_options"` + StartOptions []string `mapstructure:"start_options"` + AttachOptions []string `mapstructure:"attach_options"` Name string `mapstructure:"template_name"` Parameters []string `mapstructure:"template_parameters"` EnvVars []string `mapstructure:"template_environment_vars"` diff --git a/builder/lxc/step_lxc_create.go b/builder/lxc/step_lxc_create.go index a98926ffa..d1dbdf2d3 100644 --- a/builder/lxc/step_lxc_create.go +++ b/builder/lxc/step_lxc_create.go @@ -28,12 +28,15 @@ func (s *stepLxcCreate) Run(state multistep.StateBag) multistep.StepAction { } commands := make([][]string, 3) - commands[0] = append(config.EnvVars, []string{"lxc-create", "-n", name, "-t", config.Name, "--"}...) + commands[0] = append(config.EnvVars, "lxc-create") + commands[0] = append(commands[0], config.CreateOptions...) + commands[0] = append(commands[0], []string{"-n", name, "-t", config.Name, "--"}...) commands[0] = append(commands[0], config.Parameters...) // prevent tmp from being cleaned on boot, we put provisioning scripts there // todo: wait for init to finish before moving on to provisioning instead of this commands[1] = []string{"touch", filepath.Join(rootfs, "tmp", ".tmpfs")} - commands[2] = []string{"lxc-start", "-d", "--name", name} + commands[2] = append([]string{"lxc-start"}, config.StartOptions...) + commands[2] = append(commands[2], []string{"-d", "--name", name}...) ui.Say("Creating container...") for _, command := range commands { diff --git a/builder/lxc/step_provision.go b/builder/lxc/step_provision.go index f91eb56ce..0cf2a8bdb 100644 --- a/builder/lxc/step_provision.go +++ b/builder/lxc/step_provision.go @@ -19,6 +19,7 @@ func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction { // Create our communicator comm := &LxcAttachCommunicator{ ContainerName: config.ContainerName, + AttachOptions: config.AttachOptions, RootFs: mountPath, CmdWrapper: wrappedCommand, } diff --git a/builder/lxc/step_wait_init.go b/builder/lxc/step_wait_init.go index e5d375312..1ddda52b6 100644 --- a/builder/lxc/step_wait_init.go +++ b/builder/lxc/step_wait_init.go @@ -76,6 +76,7 @@ func (s *StepWaitInit) waitForInit(state multistep.StateBag, cancel <-chan struc comm := &LxcAttachCommunicator{ ContainerName: config.ContainerName, + AttachOptions: config.AttachOptions, RootFs: mountPath, CmdWrapper: wrappedCommand, } diff --git a/test/builder_lxc.bats b/test/builder_lxc.bats index c29030424..7173d0f7d 100644 --- a/test/builder_lxc.bats +++ b/test/builder_lxc.bats @@ -1,40 +1,106 @@ #!/usr/bin/env bats # -# This tests the lxc builder. The teardown function will -# delete any images in the output-lxc-* folders. +# This tests the lxc builder by creating minimal containers and checking that +# custom lxc container configuration files are successfully applied. The +# teardown function will delete any images in the output-lxc-* folders along +# with the auto-generated lxc container configuration files and hook scripts. #load test_helper #fixtures builder-lxc FIXTURE_ROOT="$BATS_TEST_DIRNAME/fixtures/builder-lxc" +have_command() { + command -v "$1" >/dev/null 2>&1 +} + # Required parameters -command -v lxc-create >/dev/null 2>&1 || { +have_command lxc-create || { echo "'lxc-create' must be installed via the lxc (or lxc1 for ubuntu >=16.04) package" >&2 exit 1 } +DESTROY_HOOK_SCRIPT=$FIXTURE_ROOT/destroy-hook.sh +DESTROY_HOOK_LOG=$FIXTURE_ROOT/destroy-hook.log +printf > "$DESTROY_HOOK_SCRIPT" ' +echo "$LXC_NAME" > "%s" +' "$DESTROY_HOOK_LOG" +chmod +x "$DESTROY_HOOK_SCRIPT" + +INIT_CONFIG=$FIXTURE_ROOT/lxc.custom.conf +printf > "$INIT_CONFIG" ' +lxc.hook.destroy = %s +' "$DESTROY_HOOK_SCRIPT" + teardown() { + for f in "$INIT_CONFIG" "$DESTROY_HOOK_SCRIPT" "$DESTROY_HOOK_LOG"; do + [ -e "$f" ] && rm -f "$f" + done + rm -rf output-lxc-* } -@test "lxc: build centos minimal.json" { - run packer build -var template_name=centos $FIXTURE_ROOT/minimal.json - [ "$status" -eq 0 ] - [ -f output-lxc-centos/rootfs.tar.gz ] - [ -f output-lxc-centos/lxc-config ] +assert_build() { + local template_name="$1" + shift + + local build_status=0 + + run packer build -var template_name="$template_name" "$@" + + [ "$status" -eq 0 ] || { + echo "${template_name} build exited badly: $status" >&2 + echo "$output" >&2 + build_status="$status" + } + + for expected in "output-lxc-${template_name}"/{rootfs.tar.gz,lxc-config}; do + [ -f "$expected" ] || { + echo "missing expected artifact '${expected}'" >&2 + build_status=1 + } + done + + return $build_status } +assert_container_name() { + local container_name="$1" + + [ -f "$DESTROY_HOOK_LOG" ] || { + echo "missing expected lxc.hook.destroy logfile '$DESTROY_HOOK_LOG'" + return 1 + } + + read -r lxc_name < "$DESTROY_HOOK_LOG" + + [ "$lxc_name" = "$container_name" ] +} + +@test "lxc: build centos minimal.json" { + have_command yum || skip "'yum' must be installed to build centos containers" + local container_name=packer-lxc-centos + assert_build centos -var init_config="$INIT_CONFIG" \ + -var container_name="$container_name" \ + $FIXTURE_ROOT/minimal.json + assert_container_name "$container_name" +} @test "lxc: build trusty minimal.json" { - run packer build -var template_name=ubuntu -var template_parameters="SUITE=trusty" $FIXTURE_ROOT/minimal.json - [ "$status" -eq 0 ] - [ -f output-lxc-ubuntu/rootfs.tar.gz ] - [ -f output-lxc-ubuntu/lxc-config ] + have_command debootstrap || skip "'debootstrap' must be installed to build ubuntu containers" + local container_name=packer-lxc-ubuntu + assert_build ubuntu -var init_config="$INIT_CONFIG" \ + -var container_name="$container_name" \ + -var template_parameters="SUITE=trusty" \ + $FIXTURE_ROOT/minimal.json + assert_container_name "$container_name" } @test "lxc: build debian minimal.json" { - run packer build -var template_name=debian -var template_parameters="SUITE=jessie" $FIXTURE_ROOT/minimal.json - [ "$status" -eq 0 ] - [ -f output-lxc-debian/rootfs.tar.gz ] - [ -f output-lxc-debian/lxc-config ] + have_command debootstrap || skip "'debootstrap' must be installed to build debian containers" + local container_name=packer-lxc-debian + assert_build debian -var init_config="$INIT_CONFIG" \ + -var container_name="$container_name" \ + -var template_parameters="SUITE=jessie" \ + $FIXTURE_ROOT/minimal.json + assert_container_name "$container_name" } diff --git a/test/fixtures/builder-lxc/minimal.json b/test/fixtures/builder-lxc/minimal.json index 5bf7998fd..997e48cfd 100644 --- a/test/fixtures/builder-lxc/minimal.json +++ b/test/fixtures/builder-lxc/minimal.json @@ -1,13 +1,29 @@ { "variables": { "template_name": "debian", - "template_parameters": "SUITE=jessie" + "template_parameters": "SUITE=jessie", + "container_name": "packer-lxc", + "set_var": "hello" }, + "provisioners": [ + { + "type": "shell", + "inline": [ + "if [ \"$SET_VAR\" != \"{{user `set_var`}}\" ]; then", + " echo \"Got unexpected value '$SET_VAR' for SET_VAR\" 1>&2", + " exit 1", + "fi" + ] + } + ], "builders": [ { "type": "lxc", "name": "lxc-{{user `template_name`}}", "template_name": "{{user `template_name`}}", + "container_name": "{{user `container_name`}}", + "create_options": [ "-f", "{{user `init_config`}}" ], + "attach_options": [ "--clear-env", "--set-var", "SET_VAR={{user `set_var`}}" ], "config_file": "/usr/share/lxc/config/{{user `template_name`}}.common.conf", "template_environment_vars": [ "{{user `template_parameters`}}" ] } diff --git a/website/source/docs/builders/lxc.html.md b/website/source/docs/builders/lxc.html.md index bc2b81a57..1d73ede38 100644 --- a/website/source/docs/builders/lxc.html.md +++ b/website/source/docs/builders/lxc.html.md @@ -110,3 +110,18 @@ Below is a fully functioning example. `/usr/share/lxc/templates/lxc-`. Note: This gets passed as ARGV to the template command. Ensure you have an array of strings, as a single string with spaces probably won't work. Defaults to `[]`. + +- `create_options` (array of strings) - Options to pass to `lxc-create`. For + instance, you can specify a custom LXC container configuration file with + `["-f", "/path/to/lxc.conf"]`. Defaults to `[]`. See `man 1 lxc-create` for + available options. + +- `start_options` (array of strings) - Options to pass to `lxc-start`. For + instance, you can override parameters from the LXC container configuration + file via `["--define", "KEY=VALUE"]`. Defaults to `[]`. See `man 1 + lxc-start` for available options. + +- `attach_options` (array of strings) - Options to pass to `lxc-attach`. For + instance, you can prevent the container from inheriting the host machine's + environment by specifying `["--clear-env"]`. Defaults to `[]`. See `man 1 + lxc-attach` for available options.