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.
This commit is contained in:
Matt Schreiber 2017-10-30 21:48:43 -04:00
parent fe4d4648e6
commit 1f2135f65e
No known key found for this signature in database
GPG Key ID: EF3B12854EEE31BF
8 changed files with 131 additions and 20 deletions

View File

@ -16,6 +16,7 @@ import (
type LxcAttachCommunicator struct { type LxcAttachCommunicator struct {
RootFs string RootFs string
ContainerName string ContainerName string
AttachOptions []string
CmdWrapper CommandWrapper 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) { 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) 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( 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -18,6 +18,9 @@ type Config struct {
ContainerName string `mapstructure:"container_name"` ContainerName string `mapstructure:"container_name"`
CommandWrapper string `mapstructure:"command_wrapper"` CommandWrapper string `mapstructure:"command_wrapper"`
RawInitTimeout string `mapstructure:"init_timeout"` 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"` Name string `mapstructure:"template_name"`
Parameters []string `mapstructure:"template_parameters"` Parameters []string `mapstructure:"template_parameters"`
EnvVars []string `mapstructure:"template_environment_vars"` EnvVars []string `mapstructure:"template_environment_vars"`

View File

@ -28,12 +28,15 @@ func (s *stepLxcCreate) Run(state multistep.StateBag) multistep.StepAction {
} }
commands := make([][]string, 3) 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...) commands[0] = append(commands[0], config.Parameters...)
// prevent tmp from being cleaned on boot, we put provisioning scripts there // 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 // todo: wait for init to finish before moving on to provisioning instead of this
commands[1] = []string{"touch", filepath.Join(rootfs, "tmp", ".tmpfs")} 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...") ui.Say("Creating container...")
for _, command := range commands { for _, command := range commands {

View File

@ -19,6 +19,7 @@ func (s *StepProvision) Run(state multistep.StateBag) multistep.StepAction {
// Create our communicator // Create our communicator
comm := &LxcAttachCommunicator{ comm := &LxcAttachCommunicator{
ContainerName: config.ContainerName, ContainerName: config.ContainerName,
AttachOptions: config.AttachOptions,
RootFs: mountPath, RootFs: mountPath,
CmdWrapper: wrappedCommand, CmdWrapper: wrappedCommand,
} }

View File

@ -76,6 +76,7 @@ func (s *StepWaitInit) waitForInit(state multistep.StateBag, cancel <-chan struc
comm := &LxcAttachCommunicator{ comm := &LxcAttachCommunicator{
ContainerName: config.ContainerName, ContainerName: config.ContainerName,
AttachOptions: config.AttachOptions,
RootFs: mountPath, RootFs: mountPath,
CmdWrapper: wrappedCommand, CmdWrapper: wrappedCommand,
} }

View File

@ -1,40 +1,106 @@
#!/usr/bin/env bats #!/usr/bin/env bats
# #
# This tests the lxc builder. The teardown function will # This tests the lxc builder by creating minimal containers and checking that
# delete any images in the output-lxc-* folders. # 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 #load test_helper
#fixtures builder-lxc #fixtures builder-lxc
FIXTURE_ROOT="$BATS_TEST_DIRNAME/fixtures/builder-lxc" FIXTURE_ROOT="$BATS_TEST_DIRNAME/fixtures/builder-lxc"
have_command() {
command -v "$1" >/dev/null 2>&1
}
# Required parameters # 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 echo "'lxc-create' must be installed via the lxc (or lxc1 for ubuntu >=16.04) package" >&2
exit 1 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() { teardown() {
for f in "$INIT_CONFIG" "$DESTROY_HOOK_SCRIPT" "$DESTROY_HOOK_LOG"; do
[ -e "$f" ] && rm -f "$f"
done
rm -rf output-lxc-* rm -rf output-lxc-*
} }
@test "lxc: build centos minimal.json" { assert_build() {
run packer build -var template_name=centos $FIXTURE_ROOT/minimal.json local template_name="$1"
[ "$status" -eq 0 ] shift
[ -f output-lxc-centos/rootfs.tar.gz ]
[ -f output-lxc-centos/lxc-config ] 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" { @test "lxc: build trusty minimal.json" {
run packer build -var template_name=ubuntu -var template_parameters="SUITE=trusty" $FIXTURE_ROOT/minimal.json have_command debootstrap || skip "'debootstrap' must be installed to build ubuntu containers"
[ "$status" -eq 0 ] local container_name=packer-lxc-ubuntu
[ -f output-lxc-ubuntu/rootfs.tar.gz ] assert_build ubuntu -var init_config="$INIT_CONFIG" \
[ -f output-lxc-ubuntu/lxc-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" { @test "lxc: build debian minimal.json" {
run packer build -var template_name=debian -var template_parameters="SUITE=jessie" $FIXTURE_ROOT/minimal.json have_command debootstrap || skip "'debootstrap' must be installed to build debian containers"
[ "$status" -eq 0 ] local container_name=packer-lxc-debian
[ -f output-lxc-debian/rootfs.tar.gz ] assert_build debian -var init_config="$INIT_CONFIG" \
[ -f output-lxc-debian/lxc-config ] -var container_name="$container_name" \
-var template_parameters="SUITE=jessie" \
$FIXTURE_ROOT/minimal.json
assert_container_name "$container_name"
} }

View File

@ -1,13 +1,29 @@
{ {
"variables": { "variables": {
"template_name": "debian", "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": [ "builders": [
{ {
"type": "lxc", "type": "lxc",
"name": "lxc-{{user `template_name`}}", "name": "lxc-{{user `template_name`}}",
"template_name": "{{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", "config_file": "/usr/share/lxc/config/{{user `template_name`}}.common.conf",
"template_environment_vars": [ "{{user `template_parameters`}}" ] "template_environment_vars": [ "{{user `template_parameters`}}" ]
} }

View File

@ -110,3 +110,18 @@ Below is a fully functioning example.
`/usr/share/lxc/templates/lxc-<template_name>`. Note: This gets passed as `/usr/share/lxc/templates/lxc-<template_name>`. Note: This gets passed as
ARGV to the template command. Ensure you have an array of strings, 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 `[]`. 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.