Merge pull request #34 from fgrehm/new-box-format

New box format, drop support for ubuntu cloud image and start making use of a minimal quantal 64 box built with debootstrap
This commit is contained in:
Fabio Rehm 2013-03-07 21:01:54 -08:00
commit f6b7859b77
18 changed files with 786 additions and 614 deletions

1
.gitignore vendored
View file

@ -25,4 +25,5 @@ doc/
Vagrantfile Vagrantfile
/boxes/**/*.tar.gz /boxes/**/*.tar.gz
/boxes/quantal64/rootfs-amd64/
/boxes/output/ /boxes/output/

View file

@ -37,7 +37,7 @@ sudo apt-get install lxc bsdtar
* Ruby >= 1.9.3 only, patches for 1.8.7 are welcome * Ruby >= 1.9.3 only, patches for 1.8.7 are welcome
* A hell lot of `sudo`s * A hell lot of `sudo`s
* Only a [single box supported](boxes), I'm still [figuring out what should go * Only a [single ubuntu box supported](boxes), I'm still [figuring out what should go
on the .box file](https://github.com/fgrehm/vagrant-lxc/issues/4) on the .box file](https://github.com/fgrehm/vagrant-lxc/issues/4)
* "[works](https://github.com/fgrehm/vagrant-lxc/issues/20) on [my machine](https://github.com/fgrehm/vagrant-lxc/issues/7)" (TM) * "[works](https://github.com/fgrehm/vagrant-lxc/issues/20) on [my machine](https://github.com/fgrehm/vagrant-lxc/issues/7)" (TM)
* + bunch of other [core features](https://github.com/fgrehm/vagrant-lxc/issues?labels=core&milestone=&page=1&state=open) * + bunch of other [core features](https://github.com/fgrehm/vagrant-lxc/issues?labels=core&milestone=&page=1&state=open)
@ -53,19 +53,18 @@ git clone git://github.com/fgrehm/vagrant-lxc.git --recurse
cd vagrant-lxc cd vagrant-lxc
bundle install bundle install
bundle exec rake install bundle exec rake install
bundle exec rake boxes:build:ubuntu-cloud
vagrant-lxc box add ubuntu-cloud boxes/output/ubuntu-cloud.box
``` ```
Since Vagrant 1.1 has not been released yet and to avoid messing up with you Since Vagrant 1.1 has not been released yet and to avoid messing up with you
current Vagrant installation, I've vendored Vagrant's sources from the master current Vagrant installation, I've vendored Vagrant's sources from the master
and made it available from [`vagrant-lxc`](bin/vagrant-lxc). So after installing and made it available from [`vagrant-lxc`](bin/vagrant-lxc). So after installing
`vagrant-lxc` and adding the base box, create a `Vagrantfile` like the one below `vagrant-lxc`, create a `Vagrantfile` like the one below and run
and run `vagrant-lxc up --provider=lxc`: `vagrant-lxc up --provider=lxc`:
```ruby ```ruby
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
config.vm.box = "ubuntu-cloud" config.vm.box = "lxc-quantal64"
config.vm.box_url = 'http://dl.dropbox.com/u/13510779/lxc-quantal64-2013-03-08.box'
# Create a private network, which allows host-only access to the machine # Create a private network, which allows host-only access to the machine
# using a specific IP. # using a specific IP.
@ -104,10 +103,17 @@ just sing that same old song:
git clone git://github.com/fgrehm/vagrant-lxc.git --recurse git clone git://github.com/fgrehm/vagrant-lxc.git --recurse
cd vagrant-lxc cd vagrant-lxc
bundle install bundle install
bundle exec rake boxes:build:ubuntu-cloud
bundle exec rake # to run all specs bundle exec rake # to run all specs
``` ```
To rebuild and add the new quantal64 box:
```
bundle exec rake boxes:quantal64:build
vagrant-lxc box add quantal64 boxes/output/lxc-quantal64.box
```
### Using VirtualBox for development ### Using VirtualBox for development
I've also prepared a Vagrant 1.0 VirtualBox machine for development that you can I've also prepared a Vagrant 1.0 VirtualBox machine for development that you can

103
boxes/quantal64/download-ubuntu Executable file
View file

@ -0,0 +1,103 @@
#!/bin/bash
# This is the code extracted from /usr/share/lxc/templates/lxc-ubuntu
# that comes with Ubuntu 12.10 which is responsible for downloading the
# rootfs files / packages
set -e
suggest_flush()
{
echo "Container upgrade failed. The container cache may be out of date,"
echo "in which case flushing the case (see -F in the hep output) may help."
}
cleanup()
{
rm -rf $cache/partial-$arch
rm -rf $cache/rootfs-$arch
}
write_sourceslist()
{
# $1 => path to the rootfs
# $2 => architecture we want to add
MIRROR=${MIRROR:-mirror://mirrors.ubuntu.com/mirrors.txt}
SECURITY_MIRROR=${SECURITY_MIRROR:-http://security.ubuntu.com/ubuntu}
cat >> "$1/etc/apt/sources.list" << EOF
deb $MIRROR ${release} main restricted universe multiverse
deb $MIRROR ${release}-updates main restricted universe multiverse
deb $SECURITY_MIRROR ${release}-security main restricted universe multiverse
EOF
}
download_ubuntu()
{
packages=vim,ssh,curl,wget,bash-completion
echo "installing packages: $packages"
trap cleanup EXIT SIGHUP SIGINT SIGTERM
# check the mini ubuntu was not already downloaded
mkdir -p "$cache/partial-$arch"
if [ $? -ne 0 ]; then
echo "Failed to create '$cache/partial-$arch' directory"
return 1
fi
# download a mini ubuntu into a cache
echo "Downloading ubuntu $release minimal ..."
if [ -n "$(which qemu-debootstrap)" ]; then
qemu-debootstrap --verbose --components=main,universe --arch=$arch --include=$packages $release $cache/partial-$arch $MIRROR
else
debootstrap --verbose --components=main,universe --arch=$arch --include=$packages $release $cache/partial-$arch $MIRROR
fi
if [ $? -ne 0 ]; then
echo "Failed to download the rootfs, aborting."
return 1
fi
# Serge isn't sure whether we should avoid doing this when
# $release == `distro-info -d`
echo "Installing updates"
> $cache/partial-$arch/etc/apt/sources.list
write_sourceslist $cache/partial-$arch/ $arch
chroot "$1/partial-${arch}" apt-get update
if [ $? -ne 0 ]; then
echo "Failed to update the apt cache"
return 1
fi
cat > "$1/partial-${arch}"/usr/sbin/policy-rc.d << EOF
#!/bin/sh
exit 101
EOF
chmod +x "$1/partial-${arch}"/usr/sbin/policy-rc.d
lxc-unshare -s MOUNT -- chroot "$1/partial-${arch}" apt-get dist-upgrade -y || { suggest_flush; false; }
rm -f "$1/partial-${arch}"/usr/sbin/policy-rc.d
chroot "$1/partial-${arch}" apt-get clean
mv "$1/partial-$arch" "$1/rootfs-$arch"
trap EXIT
trap SIGINT
trap SIGTERM
trap SIGHUP
echo "Download complete"
return 0
}
declare cache=`readlink -f .` \
arch=amd64 \
release=quantal
if [ -d "${cache}/rootfs-${arch}" ]; then
echo 'The rootfs cache has been downloaded already, please remove it if you want to update'
exit 1
fi
download_ubuntu $cache $arch $release

567
boxes/quantal64/lxc-template Executable file
View file

@ -0,0 +1,567 @@
#!/bin/bash
# This is a modified version of /usr/share/lxc/templates/lxc-ubuntu
# that comes with Ubuntu 12.10 changed to suit vagrant-lxc needs
#
# template script for generating ubuntu container for LXC
#
# This script consolidates and extends the existing lxc ubuntu scripts
#
# Copyright © 2011 Serge Hallyn <serge.hallyn@canonical.com>
# Copyright © 2010 Wilhelm Meier
# Author: Wilhelm Meier <wilhelm.meier@fh-kl.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2, as
# published by the Free Software Foundation.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
set -e
if [ -r /etc/default/lxc ]; then
. /etc/default/lxc
fi
configure_ubuntu()
{
rootfs=$1
hostname=$2
release=$3
# configure the network using the dhcp
cat <<EOF > $rootfs/etc/network/interfaces
# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
# The loopback network interface
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet dhcp
EOF
# set the hostname
cat <<EOF > $rootfs/etc/hostname
$hostname
EOF
# set minimal hosts
cat <<EOF > $rootfs/etc/hosts
127.0.0.1 localhost
127.0.1.1 $hostname
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
EOF
if [ ! -f $rootfs/etc/init/container-detect.conf ]; then
# suppress log level output for udev
sed -i "s/=\"err\"/=0/" $rootfs/etc/udev/udev.conf
# remove jobs for consoles 5 and 6 since we only create 4 consoles in
# this template
rm -f $rootfs/etc/init/tty{5,6}.conf
fi
chroot $rootfs useradd --create-home -s /bin/bash vagrant
echo "vagrant:vagrant" | chroot $rootfs chpasswd
return 0
}
# finish setting up the user in the container by injecting ssh key and
# adding sudo group membership.
# passed-in user is 'vagrant'
finalize_user()
{
user=$1
sudo_version=$(chroot $rootfs dpkg-query -W -f='${Version}' sudo)
if chroot $rootfs dpkg --compare-versions $sudo_version gt "1.8.3p1-1"; then
groups="sudo"
else
groups="sudo admin"
fi
for group in $groups; do
chroot $rootfs groupadd --system $group >/dev/null 2>&1 || true
chroot $rootfs adduser ${user} $group >/dev/null 2>&1 || true
done
chroot $rootfs cp /etc/sudoers /etc/sudoers.orig >/dev/null 2>&1 || true
chroot $rootfs sed -i -e 's/%sudo\s\+ALL=(ALL:ALL)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' /etc/sudoers >/dev/null 2>&1 || true
chroot $rootfs locale-gen en_US en_US.UTF-8 hu_HU hu_HU.UTF-8 >/dev/null 2>&1 || true
chroot $rootfs dpkg-reconfigure locales >/dev/null 2>&1 || true
if [ -n "$auth_key" -a -f "$auth_key" ]; then
u_path="/home/${user}/.ssh"
root_u_path="$rootfs/$u_path"
mkdir -p $root_u_path
cp $auth_key "$root_u_path/authorized_keys"
chroot $rootfs chown -R ${user}: "$u_path"
echo "Inserted SSH public key from $auth_key into /home/${user}/.ssh/authorized_keys"
fi
return 0
}
write_sourceslist()
{
# $1 => path to the rootfs
# $2 => architecture we want to add
# $3 => whether to use the multi-arch syntax or not
case $2 in
amd64|i386)
MIRROR=${MIRROR:-http://archive.ubuntu.com/ubuntu}
SECURITY_MIRROR=${SECURITY_MIRROR:-http://security.ubuntu.com/ubuntu}
;;
*)
MIRROR=${MIRROR:-http://ports.ubuntu.com/ubuntu-ports}
SECURITY_MIRROR=${SECURITY_MIRROR:-http://ports.ubuntu.com/ubuntu-ports}
;;
esac
if [ -n "$3" ]; then
cat >> "$1/etc/apt/sources.list" << EOF
deb [arch=$2] $MIRROR ${release} main restricted universe multiverse
deb [arch=$2] $MIRROR ${release}-updates main restricted universe multiverse
deb [arch=$2] $SECURITY_MIRROR ${release}-security main restricted universe multiverse
EOF
else
cat >> "$1/etc/apt/sources.list" << EOF
deb $MIRROR ${release} main restricted universe multiverse
deb $MIRROR ${release}-updates main restricted universe multiverse
deb $SECURITY_MIRROR ${release}-security main restricted universe multiverse
EOF
fi
}
copy_ubuntu()
{
cache=$1
arch=$2
rootfs=$3
# make a local copy of the miniubuntu
echo "Copying rootfs to $rootfs ..."
mkdir -p $rootfs
rsync -a $cache/rootfs-$arch/ $rootfs/ || return 1
return 0
}
install_ubuntu()
{
rootfs=$1
release=$2
cache=$3 # "/var/cache/lxc/$release"
mkdir -p /var/lock/subsys/
(
flock -x 200
if [ $? -ne 0 ]; then
echo "Cache repository is busy."
return 1
fi
echo "Copy $cache/rootfs-$arch to $rootfs ... "
copy_ubuntu $cache $arch $rootfs
if [ $? -ne 0 ]; then
echo "Failed to copy rootfs"
return 1
fi
return 0
) 200>/var/lock/subsys/lxc
return $?
}
copy_configuration()
{
path=$1
rootfs=$2
name=$3
arch=$4
release=$5
if [ $arch = "i386" ]; then
arch="i686"
fi
ttydir=""
if [ -f $rootfs/etc/init/container-detect.conf ]; then
ttydir=" lxc"
fi
# if there is exactly one veth network entry, make sure it has an
# associated hwaddr.
nics=`grep -e '^lxc\.network\.type[ \t]*=[ \t]*veth' $path/config | wc -l`
if [ $nics -eq 1 ]; then
grep -q "^lxc.network.hwaddr" $path/config || cat <<EOF >> $path/config
lxc.network.hwaddr = 00:16:3e:$(openssl rand -hex 3| sed 's/\(..\)/\1:/g; s/.$//')
EOF
fi
grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config
cat <<EOF >> $path/config
lxc.utsname = $name
lxc.devttydir =$ttydir
lxc.tty = 4
lxc.pts = 1024
lxc.mount = $path/fstab
lxc.arch = $arch
lxc.cap.drop = sys_module mac_admin mac_override
lxc.pivotdir = lxc_putold
# uncomment the next line to run the container unconfined:
#lxc.aa_profile = unconfined
lxc.cgroup.devices.deny = a
# Allow any mknod (but not using the node)
lxc.cgroup.devices.allow = c *:* m
lxc.cgroup.devices.allow = b *:* m
# /dev/null and zero
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm
# consoles
lxc.cgroup.devices.allow = c 5:1 rwm
lxc.cgroup.devices.allow = c 5:0 rwm
#lxc.cgroup.devices.allow = c 4:0 rwm
#lxc.cgroup.devices.allow = c 4:1 rwm
# /dev/{,u}random
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 1:8 rwm
lxc.cgroup.devices.allow = c 136:* rwm
lxc.cgroup.devices.allow = c 5:2 rwm
# rtc
lxc.cgroup.devices.allow = c 254:0 rwm
#fuse
lxc.cgroup.devices.allow = c 10:229 rwm
#tun
lxc.cgroup.devices.allow = c 10:200 rwm
#full
lxc.cgroup.devices.allow = c 1:7 rwm
#hpet
lxc.cgroup.devices.allow = c 10:228 rwm
#kvm
lxc.cgroup.devices.allow = c 10:232 rwm
EOF
cat <<EOF > $path/fstab
proc proc proc nodev,noexec,nosuid 0 0
sysfs sys sysfs defaults 0 0
EOF
if [ $? -ne 0 ]; then
echo "Failed to add configuration"
return 1
fi
return 0
}
trim()
{
rootfs=$1
release=$2
# provide the lxc service
cat <<EOF > $rootfs/etc/init/lxc.conf
# fake some events needed for correct startup other services
description "Container Upstart"
start on startup
script
rm -rf /var/run/*.pid
rm -rf /var/run/network/*
/sbin/initctl emit stopped JOB=udevtrigger --no-wait
/sbin/initctl emit started JOB=udev --no-wait
end script
EOF
# fix buggus runlevel with sshd
cat <<EOF > $rootfs/etc/init/ssh.conf
# ssh - OpenBSD Secure Shell server
#
# The OpenSSH server provides secure shell access to the system.
description "OpenSSH server"
start on filesystem
stop on runlevel [!2345]
expect fork
respawn
respawn limit 10 5
umask 022
# replaces SSHD_OOM_ADJUST in /etc/default/ssh
oom never
pre-start script
test -x /usr/sbin/sshd || { stop; exit 0; }
test -e /etc/ssh/sshd_not_to_be_run && { stop; exit 0; }
test -c /dev/null || { stop; exit 0; }
mkdir -p -m0755 /var/run/sshd
end script
# if you used to set SSHD_OPTS in /etc/default/ssh, you can change the
# 'exec' line here instead
exec /usr/sbin/sshd
EOF
cat <<EOF > $rootfs/etc/init/console.conf
# console - getty
#
# This service maintains a console on tty1 from the point the system is
# started until it is shut down again.
start on stopped rc RUNLEVEL=[2345]
stop on runlevel [!2345]
respawn
exec /sbin/getty -8 38400 /dev/console
EOF
cat <<EOF > $rootfs/lib/init/fstab
# /lib/init/fstab: cleared out for bare-bones lxc
EOF
# reconfigure some services
if [ -z "$LANG" ]; then
chroot $rootfs locale-gen en_US.UTF-8
chroot $rootfs update-locale LANG=en_US.UTF-8
else
chroot $rootfs locale-gen $LANG
chroot $rootfs update-locale LANG=$LANG
fi
# remove pointless services in a container
chroot $rootfs /usr/sbin/update-rc.d -f ondemand remove
chroot $rootfs /bin/bash -c 'cd /etc/init; for f in $(ls u*.conf); do mv $f $f.orig; done'
chroot $rootfs /bin/bash -c 'cd /etc/init; for f in $(ls tty[2-9].conf); do mv $f $f.orig; done'
chroot $rootfs /bin/bash -c 'cd /etc/init; for f in $(ls plymouth*.conf); do mv $f $f.orig; done'
chroot $rootfs /bin/bash -c 'cd /etc/init; for f in $(ls hwclock*.conf); do mv $f $f.orig; done'
chroot $rootfs /bin/bash -c 'cd /etc/init; for f in $(ls module*.conf); do mv $f $f.orig; done'
# if this isn't lucid, then we need to twiddle the network upstart bits :(
if [ $release != "lucid" ]; then
sed -i 's/^.*emission handled.*$/echo Emitting lo/' $rootfs/etc/network/if-up.d/upstart
fi
}
post_process()
{
rootfs=$1
release=$2
trim_container=$3
if [ $trim_container -eq 1 ]; then
trim $rootfs $release
elif [ ! -f $rootfs/etc/init/container-detect.conf ]; then
# Make sure we have a working resolv.conf
cresolvonf="${rootfs}/etc/resolv.conf"
mv $cresolvonf ${cresolvonf}.lxcbak
cat /etc/resolv.conf > ${cresolvonf}
# for lucid, if not trimming, then add the ubuntu-virt
# ppa and install lxcguest
if [ $release = "lucid" ]; then
chroot $rootfs apt-get update
chroot $rootfs apt-get install --force-yes -y python-software-properties
chroot $rootfs add-apt-repository ppa:ubuntu-virt/ppa
fi
chroot $rootfs apt-get update
chroot $rootfs apt-get install --force-yes -y lxcguest
# Restore old resolv.conf
rm -f ${cresolvonf}
mv ${cresolvonf}.lxcbak ${cresolvonf}
fi
# If the container isn't running a native architecture, setup multiarch
if [ -x "$(ls -1 ${rootfs}/usr/bin/qemu-*-static 2>/dev/null)" ]; then
dpkg_version=$(chroot $rootfs dpkg-query -W -f='${Version}' dpkg)
if chroot $rootfs dpkg --compare-versions $dpkg_version ge "1.16.2"; then
chroot $rootfs dpkg --add-architecture ${hostarch}
else
mkdir -p ${rootfs}/etc/dpkg/dpkg.cfg.d
echo "foreign-architecture ${hostarch}" > ${rootfs}/etc/dpkg/dpkg.cfg.d/lxc-multiarch
fi
# Save existing value of MIRROR and SECURITY_MIRROR
DEFAULT_MIRROR=$MIRROR
DEFAULT_SECURITY_MIRROR=$SECURITY_MIRROR
# Write a new sources.list containing both native and multiarch entries
> ${rootfs}/etc/apt/sources.list
write_sourceslist $rootfs $arch "native"
MIRROR=$DEFAULT_MIRROR
SECURITY_MIRROR=$DEFAULT_SECURITY_MIRROR
write_sourceslist $rootfs $hostarch "multiarch"
# Finally update the lists and install upstart using the host architecture
chroot $rootfs apt-get update
chroot $rootfs apt-get install --force-yes -y --no-install-recommends upstart:${hostarch} mountall:${hostarch} iproute:${hostarch} isc-dhcp-client:${hostarch}
fi
# rmdir /dev/shm for containers that have /run/shm
# I'm afraid of doing rm -rf $rootfs/dev/shm, in case it did
# get bind mounted to the host's /run/shm. So try to rmdir
# it, and in case that fails move it out of the way.
if [ ! -L $rootfs/dev/shm ] && [ -d $rootfs/run/shm ] && [ -e $rootfs/dev/shm ]; then
mv $rootfs/dev/shm $rootfs/dev/shm.bak
ln -s /run/shm $rootfs/dev/shm
fi
}
usage()
{
cat <<EOF
$1 -h|--help [-a|--arch] [--trim] [-d|--debug]
[-F | --flush-cache] [-r|--release <release>] [ -S | --auth-key <keyfile>]
release: the ubuntu release (e.g. precise): defaults to host release on ubuntu, otherwise uses latest LTS
trim: make a minimal (faster, but not upgrade-safe) container
arch: the container architecture (e.g. amd64): defaults to host arch
auth-key: SSH Public key file to inject into container
EOF
return 0
}
options=$(getopt -o a:b:hp:r:xn:FS:d:C -l arch:,help,path:,release:,trim,name:,flush-cache,auth-key:,debug:,cache: -- "$@")
if [ $? -ne 0 ]; then
usage $(basename $0)
exit 1
fi
eval set -- "$options"
release=precise # Default to the last Ubuntu LTS release for non-Ubuntu systems
if [ -f /etc/lsb-release ]; then
. /etc/lsb-release
if [ "$DISTRIB_ID" = "Ubuntu" ]; then
release=$DISTRIB_CODENAME
fi
fi
arch=$(arch)
# Code taken from debootstrap
if [ -x /usr/bin/dpkg ] && /usr/bin/dpkg --print-architecture >/dev/null 2>&1; then
arch=`/usr/bin/dpkg --print-architecture`
elif type udpkg >/dev/null 2>&1 && udpkg --print-architecture >/dev/null 2>&1; then
arch=`/usr/bin/udpkg --print-architecture`
else
arch=$(arch)
if [ "$arch" = "i686" ]; then
arch="i386"
elif [ "$arch" = "x86_64" ]; then
arch="amd64"
elif [ "$arch" = "armv7l" ]; then
arch="armel"
fi
fi
debug=0
trim_container=0
hostarch=$arch
while true
do
case "$1" in
-h|--help) usage $0 && exit 0;;
-p|--path) path=$2; shift 2;;
-n|--name) name=$2; shift 2;;
-C|--cache) cache=$2; shift 2;;
-r|--release) release=$2; shift 2;;
-a|--arch) arch=$2; shift 2;;
-x|--trim) trim_container=1; shift 1;;
-S|--auth-key) auth_key=$2; shift 2;;
-d|--debug) debug=1; shift 1;;
--) shift 1; break ;;
*) break ;;
esac
done
if [ $debug -eq 1 ]; then
set -x
fi
if [ "$arch" == "i686" ]; then
arch=i386
fi
if [ $hostarch = "i386" -a $arch = "amd64" ]; then
echo "can't create amd64 container on i386"
exit 1
fi
if [ -z "$path" ]; then
echo "'path' parameter is required"
exit 1
fi
if [ "$(id -u)" != "0" ]; then
echo "This script should be run as 'root'"
exit 1
fi
# detect rootfs
config="$path/config"
if grep -q '^lxc.rootfs' $config 2>/dev/null ; then
rootfs=`grep 'lxc.rootfs =' $config | awk -F= '{ print $2 }'`
else
rootfs=$path/rootfs
fi
install_ubuntu $rootfs $release $cache
if [ $? -ne 0 ]; then
echo "failed to install ubuntu $release"
exit 1
fi
configure_ubuntu $rootfs $name $release
if [ $? -ne 0 ]; then
echo "failed to configure ubuntu $release for a container"
exit 1
fi
copy_configuration $path $rootfs $name $arch $release
if [ $? -ne 0 ]; then
echo "failed write configuration file"
exit 1
fi
post_process $rootfs $release $trim_container
finalize_user vagrant
echo ""
echo "##"
echo "# The default user is 'vagrant' with password 'vagrant'!"
echo "# Use the 'sudo' command to run tasks as root in the container."
echo "##"
echo ""

View file

@ -0,0 +1,9 @@
{
"provider": "lxc",
"vagrant-lxc-version": "0.0.1",
"template-opts": {
"--arch": "amd64",
"--release": "quantal"
}
}

View file

@ -1,7 +0,0 @@
{
"provider": "lxc",
"vagrant-lxc-version": "0.0.1",
"template-name": "ubuntu-cloud",
"after-create-script": "setup-vagrant-user.sh",
"tar-cache": "ubuntu-12.10-server-cloudimg-amd64-root.tar.gz"
}

View file

@ -1,59 +0,0 @@
#!/bin/bash
# Argument = -r <path/to/rootfs> -i <container-ip> -k <vagrant-private-key-path>
CONTAINER_ROOTFS=
CONTAINER_IP=
VAGRANT_PRIVATE_KEY_PATH=
options=$(getopt -o r:i:k: -- "$@")
eval set -- "$options"
declare r CONTAINER_ROOTFS \
i CONTAINER_IP \
k VAGRANT_PRIVATE_KEY_PATH
while true
do
case "$1" in
-r) CONTAINER_ROOTFS=$2; shift 2;;
-i) CONTAINER_IP=$2; shift 2;;
-k) VAGRANT_PRIVATE_KEY_PATH=$2; shift 2;;
*) break ;;
esac
done
if [[ -z $CONTAINER_ROOTFS ]] || [[ -z $CONTAINER_IP ]] || [[ -z $VAGRANT_PRIVATE_KEY_PATH ]]
then
echo 'You forgot an argument!'
exit 1
fi
remote_setup_script() {
cat << EOF
useradd -d /home/vagrant -m vagrant -r -s /bin/bash
usermod -a -G admin vagrant
cp /etc/sudoers /etc/sudoers.orig
sed -i -e '/Defaults\s\+env_reset/a Defaults\texempt_group=admin' /etc/sudoers
sed -i -e 's/%admin\s\+ALL=(ALL)\s\+ALL/%admin ALL=NOPASSWD:ALL/g' /etc/sudoers
service sudo restart
sudo su vagrant -c "mkdir -p /home/vagrant/.ssh"
sudo su vagrant -c "curl -s -o /home/vagrant/.ssh/authorized_keys https://raw.github.com/mitchellh/vagrant/master/keys/vagrant.pub"
sudo apt-get update && sudo apt-get install -y puppet
EOF
}
REMOTE_SETUP_SCRIPT_PATH="/tmp/setup-vagrant-user"
# Ensures the private key has the right permissions
# Might not be needed after: https://github.com/mitchellh/vagrant/commit/d304cca35d19c5bd370330c74f003b6ac46e7f4a
chmod 0600 $VAGRANT_PRIVATE_KEY_PATH
remote_setup_script > "${CONTAINER_ROOTFS}${REMOTE_SETUP_SCRIPT_PATH}"
chmod +x "${CONTAINER_ROOTFS}${REMOTE_SETUP_SCRIPT_PATH}"
ssh ubuntu@"$CONTAINER_IP" \
-o 'StrictHostKeyChecking no' \
-o 'UserKnownHostsFile /dev/null' \
-i $VAGRANT_PRIVATE_KEY_PATH \
-- \
sudo $REMOTE_SETUP_SCRIPT_PATH

View file

@ -1,425 +0,0 @@
#!/bin/bash
# This is a modified version of /usr/share/lxc/templates/lxc-ubuntu-cloudimg
# that comes with Ubuntu 12.10
# Search for ORIGINAL to find out what has been changed
# template script for generating ubuntu container for LXC based on released cloud
# images
#
# Copyright © 2012 Serge Hallyn <serge.hallyn@canonical.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2, as
# published by the Free Software Foundation.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
set -e
if [ -r /etc/default/lxc ]; then
. /etc/default/lxc
fi
copy_configuration()
{
path=$1
rootfs=$2
name=$3
arch=$4
release=$5
if [ $arch = "i386" ]; then
arch="i686"
fi
# if there is exactly one veth network entry, make sure it has an
# associated hwaddr.
nics=`grep -e '^lxc\.network\.type[ \t]*=[ \t]*veth' $path/config | wc -l`
if [ $nics -eq 1 ]; then
grep -q "^lxc.network.hwaddr" $path/config || cat <<EOF >> $path/config
lxc.network.hwaddr = 00:16:3e:$(openssl rand -hex 3| sed 's/\(..\)/\1:/g; s/.$//')
EOF
fi
grep -q "^lxc.rootfs" $path/config 2>/dev/null || echo "lxc.rootfs = $rootfs" >> $path/config
cat <<EOF >> $path/config
lxc.utsname = $name
lxc.tty = 4
lxc.pts = 1024
lxc.mount = $path/fstab
lxc.arch = $arch
lxc.cap.drop = sys_module mac_admin
lxc.pivotdir = lxc_putold
# uncomment the next line to run the container unconfined:
#lxc.aa_profile = unconfined
lxc.cgroup.devices.deny = a
# Allow any mknod (but not using the node)
lxc.cgroup.devices.allow = c *:* m
lxc.cgroup.devices.allow = b *:* m
# /dev/null and zero
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm
# consoles
lxc.cgroup.devices.allow = c 5:1 rwm
lxc.cgroup.devices.allow = c 5:0 rwm
#lxc.cgroup.devices.allow = c 4:0 rwm
#lxc.cgroup.devices.allow = c 4:1 rwm
# /dev/{,u}random
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 1:8 rwm
lxc.cgroup.devices.allow = c 136:* rwm
lxc.cgroup.devices.allow = c 5:2 rwm
# rtc
lxc.cgroup.devices.allow = c 254:0 rwm
#fuse
lxc.cgroup.devices.allow = c 10:229 rwm
#tun
lxc.cgroup.devices.allow = c 10:200 rwm
#full
lxc.cgroup.devices.allow = c 1:7 rwm
#hpet
lxc.cgroup.devices.allow = c 10:228 rwm
#kvm
lxc.cgroup.devices.allow = c 10:232 rwm
EOF
cat <<EOF > $path/fstab
proc proc proc nodev,noexec,nosuid 0 0
sysfs sys sysfs defaults 0 0
EOF
# rmdir /dev/shm for containers that have /run/shm
# I'm afraid of doing rm -rf $rootfs/dev/shm, in case it did
# get bind mounted to the host's /run/shm. So try to rmdir
# it, and in case that fails move it out of the way.
if [ ! -L $rootfs/dev/shm ] && [ -d $rootfs/run/shm ] && [ -e $rootfs/dev/shm ]; then
mv $rootfs/dev/shm $rootfs/dev/shm.bak
ln -s /run/shm $rootfs/dev/shm
fi
return 0
}
usage()
{
cat <<EOF
LXC Container configuration for Ubuntu Cloud images.
Generic Options
[ -r | --release <release> ]: Release name of container, defaults to host
[ -a | --arch ]: Arhcitecture of container, defaults to host arcitecture
[ -C | --cloud ]: Configure container for use with meta-data service, defaults to no
[ -T | --tarball ]: Location of tarball
[ -d | --debug ]: Run with 'set -x' to debug errors
[ -s | --stream]: Use specified stream rather than 'released'
[ --cache-path]: Sets cache path used by vagrant-lxc
Options, mutually exclusive of "-C" and "--cloud":
[ -i | --hostid ]: HostID for cloud-init, defaults to random string
[ -u | --userdata ]: Cloud-init user-data file to configure container on start
[ -S | --auth-key ]: SSH Public key file to inject into container
[ -L | --nolocales ]: Do not copy host's locales into container
EOF
return 0
}
# ORIGINAL options=$(getopt -o a:hp:r:n:Fi:CLS:T:ds:u: -l arch:,help,path:,release:,name:,flush-cache,hostid:,auth-key:,cloud,no_locales,tarball:,debug,stream:,userdata: -- "$@")
# NEW one introduces the --cache-path option
options=$(getopt -o a:hp:r:n:Fi:CLS:T:ds:u: -l arch:,help,path:,release:,name:,flush-cache,hostid:,auth-key:,cloud,no_locales,tarball:,debug,stream:,userdata:,cache-path: -- "$@")
if [ $? -ne 0 ]; then
usage $(basename $0)
exit 1
fi
eval set -- "$options"
release=lucid
if [ -f /etc/lsb-release ]; then
. /etc/lsb-release
case "$DISTRIB_CODENAME" in
lucid|natty|oneiric|precise|quantal)
release=$DISTRIB_CODENAME
;;
esac
fi
arch=$(arch)
# Code taken from debootstrap
if [ -x /usr/bin/dpkg ] && /usr/bin/dpkg --print-architecture >/dev/null 2>&1; then
arch=`/usr/bin/dpkg --print-architecture`
elif type udpkg >/dev/null 2>&1 && udpkg --print-architecture >/dev/null 2>&1; then
arch=`/usr/bin/udpkg --print-architecture`
else
arch=$(arch)
if [ "$arch" = "i686" ]; then
arch="i386"
elif [ "$arch" = "x86_64" ]; then
arch="amd64"
elif [ "$arch" = "armv7l" ]; then
# note: arm images don't exist before oneiric; are called armhf in
# precise and later; and are not supported by the query, so we don't actually
# support them yet (see check later on). When Query2 is available,
# we'll use that to enable arm images.
arch="armel"
fi
fi
debug=0
hostarch=$arch
cloud=0
locales=1
flushcache=0
stream="released"
# ORIGINAL version didn't have this option
cache=
while true
do
case "$1" in
-h|--help) usage $0 && exit 0;;
-p|--path) path=$2; shift 2;;
-n|--name) name=$2; shift 2;;
-F|--flush-cache) flushcache=1; shift 1;;
-r|--release) release=$2; shift 2;;
-a|--arch) arch=$2; shift 2;;
-i|--hostid) host_id=$2; shift 2;;
-u|--userdata) userdata=$2; shift 2;;
-C|--cloud) cloud=1; shift 1;;
-S|--auth-key) auth_key=$2; shift 2;;
-L|--no_locales) locales=0; shift 1;;
-T|--tarball) tarball=$2; shift 2;;
-d|--debug) debug=1; shift 1;;
-s|--stream) stream=$2; shift 2;;
# ORIGINAL version didn't have --cache-path option
--cache-path) cache=$2; shift 2;;
--) shift 1; break ;;
*) break ;;
esac
done
if [ $debug -eq 1 ]; then
set -x
fi
if [ "$arch" == "i686" ]; then
arch=i386
fi
if [ $hostarch = "i386" -a $arch = "amd64" ]; then
echo "can't create amd64 container on i386"
exit 1
fi
if [ $arch != "i386" -a $arch != "amd64" ]; then
echo "Only i386 and amd64 are supported by the ubuntu cloud template."
exit 1
fi
if [ "$stream" != "daily" -a "$stream" != "released" ]; then
echo "Only 'daily' and 'released' streams are supported"
exit 1
fi
if [ -n "$userdata" ]; then
if [ ! -f "$userdata" ]; then
echo "Userdata ($userdata) does not exist"
exit 1
else
userdata=`readlink -f $userdata`
fi
fi
if [ -n "$auth_key" ]; then
if [ ! -f "$auth_key" ]; then
echo "--auth-key=${auth_key} must reference a file"
exit 1
fi
auth_key=$(readlink -f "${auth_key}") ||
{ echo "failed to get full path for auth_key"; exit 1; }
fi
if [ -z "$path" ]; then
echo "'path' parameter is required"
exit 1
fi
if [ "$(id -u)" != "0" ]; then
echo "This script should be run as 'root'"
exit 1
fi
# ORIGINAL version didn't have the cache-path option
if [ -z "$cache" ]; then
echo "'--cache-path' parameter is required"
exit 1
fi
# detect rootfs
config="$path/config"
if grep -q '^lxc.rootfs' $config 2>/dev/null ; then
rootfs=`grep 'lxc.rootfs =' $config | awk -F= '{ print $2 }'`
else
rootfs=$path/rootfs
fi
type ubuntu-cloudimg-query
type wget
# determine the url, tarball, and directory names
# download if needed
# ORIGINAL: cache="/var/cache/lxc/cloud-$release"
# NEW: cache can be provided with --cache-path so we can use vagrant's box directory path
mkdir -p $cache
if [ -n "$tarball" ]; then
url2="$tarball"
else
url1=`ubuntu-cloudimg-query $release $stream $arch --format "%{url}\n"`
url2=`echo $url1 | sed -e 's/.tar.gz/-root\0/'`
fi
filename=`basename $url2`
wgetcleanup()
{
rm -f $filename
}
buildcleanup()
{
cd $rootfs
umount -l $cache/$xdir || true
rm -rf $cache
}
# if the release doesn't have a *-rootfs.tar.gz, then create one from the
# cloudimg.tar.gz by extracting the .img, mounting it loopback, and creating
# a tarball from the mounted image.
build_root_tgz()
{
url=$1
filename=$2
xdir=`mktemp -d -p .`
tarname=`basename $url`
imgname="$release-*-cloudimg-$arch.img"
trap buildcleanup EXIT SIGHUP SIGINT SIGTERM
if [ $flushcache -eq 1 -o ! -f $cache/$tarname ]; then
rm -f $tarname
echo "Downloading cloud image from $url"
wget $url || { echo "Couldn't find cloud image $url."; exit 1; }
fi
echo "Creating new cached cloud image rootfs"
tar --wildcards -zxf $tarname $imgname
mount -o loop $imgname $xdir
(cd $xdir; tar zcf ../$filename .)
umount $xdir
rm -f $tarname $imgname
rmdir $xdir
echo "New cloud image cache created"
trap EXIT
trap SIGHUP
trap SIGINT
trap SIGTERM
}
mkdir -p /var/lock/subsys/
(
flock -x 200
cd $cache
if [ $flushcache -eq 1 ]; then
echo "Clearing the cached images"
rm -f $filename
fi
trap wgetcleanup EXIT SIGHUP SIGINT SIGTERM
if [ ! -f $filename ]; then
wget $url2 || build_root_tgz $url1 $filename
fi
trap EXIT
trap SIGHUP
trap SIGINT
trap SIGTERM
echo "Extracting container rootfs"
mkdir -p $rootfs
cd $rootfs
tar --numeric-owner -zxf $cache/$filename
if [ $cloud -eq 0 ]; then
echo "Configuring for running outside of a cloud environment"
echo "If you want to configure for a cloud evironment, please use '-- -C' to create the container"
seed_d=$rootfs/var/lib/cloud/seed/nocloud-net
rhostid=$(uuidgen | cut -c -8)
host_id=${hostid:-$rhostid}
mkdir -p $seed_d
cat > "$seed_d/meta-data" <<EOF
instance-id: lxc-$host_id
EOF
if [ -n "$auth_key" ]; then
{
echo "public-keys:" &&
sed -e '/^$/d' -e 's,^,- ,' "$auth_key" "$auth_key"
} >> "$seed_d/meta-data"
[ $? -eq 0 ] ||
{ echo "failed to write public keys to metadata"; exit 1; }
fi
rm $rootfs/etc/hostname
if [ $locales -eq 1 ]; then
cp /usr/lib/locale/locale-archive $rootfs/usr/lib/locale/locale-archive
fi
if [ -f "$userdata" ]; then
echo "Using custom user-data"
cp $userdata $seed_d/user-data
else
if [ -z "$MIRROR" ]; then
MIRROR="http://archive.ubuntu.com/ubuntu"
fi
cat > "$seed_d/user-data" <<EOF
#cloud-config
output: {all: '| tee -a /var/log/cloud-init-output.log'}
apt_mirror: $MIRROR
manage_etc_hosts: localhost
locale: $(/usr/bin/locale | awk -F= '/LANG=/ {print$NF}')
password: ubuntu
chpasswd: { expire: False }
EOF
fi
else
echo "Configured for running in a cloud environment."
echo "If you do not have a meta-data service, this container will likely be useless."
fi
) 200>/var/lock/subsys/lxc-ubucloud
copy_configuration $path $rootfs $name $arch $release
echo "Container $name created."
exit 0
# vi: ts=4 expandtab

23
example/Vagrantfile vendored
View file

@ -4,26 +4,37 @@
Vagrant.require_plugin 'vagrant-lxc' Vagrant.require_plugin 'vagrant-lxc'
Vagrant.configure("2") do |config| Vagrant.configure("2") do |config|
config.vm.box = "ubuntu-cloud" config.vm.box = "quantal64"
config.vm.hostname = 'ubuntu-cloud-box' config.vm.box_url = 'http://dl.dropbox.com/u/13510779/lxc-quantal64-2013-03-08.box'
config.vm.hostname = 'lxc-quantal64'
config.vm.network :private_network, ip: "192.168.33.10" config.vm.network :private_network, ip: "192.168.33.10"
config.vm.synced_folder "/tmp", "/vagrant_data" config.vm.synced_folder "/tmp", "/vagrant_data"
config.vm.provider :lxc do |lxc| config.vm.provider :lxc do |lxc|
config.vm.box_url = '../boxes/output/ubuntu-cloud.box'
lxc.start_opts << 'lxc.cgroup.memory.limit_in_bytes=400M' lxc.start_opts << 'lxc.cgroup.memory.limit_in_bytes=400M'
lxc.start_opts << 'lxc.cgroup.memory.memsw.limit_in_bytes=500M' lxc.start_opts << 'lxc.cgroup.memory.memsw.limit_in_bytes=500M'
end end
config.vm.provision :shell, :inline => 'echo "Hi there I\'m a shell script used for provisioning"' config.vm.provision :shell, :inline => <<-SCRIPT
echo "Hi there I'm a shell script used for provisioning"
if `which puppet > /dev/null`; then
echo "Looks like puppet has already been installed, moving on"
exit 0
fi
echo "I'm about to install puppet!"
sleep 1
echo "Hang tight, I just need to wait for a while since vagrant-lxc is not handling network properly ;)"
sleep 5
sudo apt-get update
sudo apt-get install puppet -y
SCRIPT
config.vm.provision :puppet do |puppet| config.vm.provision :puppet do |puppet|
puppet.module_path = "puppet/modules" puppet.module_path = "puppet/modules"
puppet.manifests_path = "puppet/manifests" puppet.manifests_path = "puppet/manifests"
puppet.manifest_file = "site.pp" puppet.manifest_file = "site.pp"
# If you want to make some noise # If you want to make some debugging noise
# puppet.options << [ '--verbose', '--debug' ] # puppet.options << [ '--verbose', '--debug' ]
end end
end end

View file

@ -1,6 +1,5 @@
require 'vagrant-lxc/action/base_action' require 'vagrant-lxc/action/base_action'
require 'vagrant-lxc/action/after_create'
require 'vagrant-lxc/action/boot' require 'vagrant-lxc/action/boot'
require 'vagrant-lxc/action/check_created' require 'vagrant-lxc/action/check_created'
require 'vagrant-lxc/action/check_running' require 'vagrant-lxc/action/check_running'
@ -54,7 +53,6 @@ module Vagrant
b.use ForwardPorts b.use ForwardPorts
b.use SaneDefaults b.use SaneDefaults
b.use Customize b.use Customize
b.use AfterCreate
b.use Boot b.use Boot
end end
end end

View file

@ -1,15 +0,0 @@
module Vagrant
module LXC
module Action
class AfterCreate < BaseAction
def call(env)
# Continue, we need the VM to be booted.
@app.call env
if env[:just_created] && (script = env[:machine].box.metadata['after-create-script'])
env[:machine].provider.container.run_after_create_script script
end
end
end
end
end
end

View file

@ -4,6 +4,7 @@ module Vagrant
# Prepare arguments to be used for lxc-create # Prepare arguments to be used for lxc-create
class HandleBoxMetadata < BaseAction class HandleBoxMetadata < BaseAction
LXC_TEMPLATES_PATH = Pathname.new("/usr/share/lxc/templates") LXC_TEMPLATES_PATH = Pathname.new("/usr/share/lxc/templates")
TEMP_PREFIX = "vagrant-lxc-rootfs-temp-"
def initialize(app, env) def initialize(app, env)
super super
@ -11,30 +12,40 @@ module Vagrant
end end
def call(env) def call(env)
# We _could_ extract the rootfs to a folder under ~/.vagrant.d/boxes
# but it would open up for a few issues:
# * The rootfs owner is the root user, so we'd need to prepend "sudo" to
# `vagrant box remove`
# * We'd waste a lot of disk space: a compressed Ubuntu rootfs fits 80mb,
# extracted it takes 262mb
# * If something goes wrong during the Container creation process and
# somehow we don't handle, writing to /tmp means that things will get
# flushed on next reboot
rootfs_cache = Dir.mktmpdir(TEMP_PREFIX)
box = env[:machine].box box = env[:machine].box
metadata = box.metadata template_name = "vagrant-#{box.name}"
template_name = metadata['template-name']
after_create = metadata['after-create-script'] ?
box.directory.join(metadata['after-create-script']).to_s :
nil
metadata.merge!(
'template-name' => "vagrant-#{box.name}-#{template_name}",
'lxc-cache-path' => box.directory.to_s,
'after-create-script' => after_create
)
# Prepends "lxc-" to the template file so that `lxc-create` is able to find it # Prepends "lxc-" to the template file so that `lxc-create` is able to find it
dest = LXC_TEMPLATES_PATH.join("lxc-#{metadata['template-name']}").to_s lxc_template_src = box.directory.join('lxc-template').to_s
src = box.directory.join(template_name).to_s unless File.exists?(lxc_template_src)
raise Errors::TemplateFileMissing.new name: box.name
end
dest = LXC_TEMPLATES_PATH.join("lxc-#{template_name}").to_s
@logger.debug('Copying LXC template into place') @logger.debug('Copying LXC template into place')
# This should only ask for administrative permission once, even system(%Q[sudo su root -c "cp #{lxc_template_src} #{dest}"])
# though its executed in multiple subshells.
system(%Q[sudo su root -c "cp #{src} #{dest}"]) @logger.debug('Extracting rootfs')
system(%Q[sudo su root -c "cd #{box.directory} && tar xfz rootfs.tar.gz -C #{rootfs_cache}"])
box.metadata.merge!(
'template-name' => template_name,
'rootfs-cache-path' => rootfs_cache
)
@app.call(env) @app.call(env)
ensure
system %Q[sudo su root -c "rm -rf #{rootfs_cache}"]
end end
end end
end end

View file

@ -33,10 +33,13 @@ module Vagrant
def create(metadata = {}) def create(metadata = {})
# FIXME: Ruby 1.8 users dont have SecureRandom # FIXME: Ruby 1.8 users dont have SecureRandom
# @logger.info('Creating container...') @logger.debug('Creating container using lxc-create...')
@name = SecureRandom.hex(6) @name = SecureRandom.hex(6)
public_key = Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s public_key = Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s
meta_opts = metadata.fetch('template-opts', {}).to_a.flatten
# TODO: Handle errors # TODO: Handle errors
lxc :create, lxc :create,
# lxc-create options # lxc-create options
@ -44,28 +47,13 @@ module Vagrant
'--name', @name, '--name', @name,
'--', '--',
# Template options # Template options
'-S', public_key, '--auth-key', public_key,
'--cache-path', metadata['lxc-cache-path'], '--cache', metadata['rootfs-cache-path'],
'-T', metadata['tar-cache'] *meta_opts
@name @name
end end
def run_after_create_script(script)
private_key = Vagrant.source_root.join('keys', 'vagrant').expand_path.to_s
# TODO: Gotta write somewhere that it has to be indempotent
retryable(:tries => 5, :sleep => 2) do
@logger.debug 'Attempt to run after-create-script from box metadata'
execute *[
script,
'-r', rootfs_path.to_s,
'-k', private_key,
'-i', dhcp_ip
]
end
end
def rootfs_path def rootfs_path
Pathname.new("#{CONTAINERS_PATH}/#{@name}/rootfs") Pathname.new("#{CONTAINERS_PATH}/#{@name}/rootfs")
end end

View file

@ -4,6 +4,9 @@ module Vagrant
class ExecuteError < Vagrant::Errors::VagrantError class ExecuteError < Vagrant::Errors::VagrantError
error_key(:lxc_execute_error) error_key(:lxc_execute_error)
end end
class TemplateFileMissing < Vagrant::Errors::VagrantError
error_key(:lxc_template_file_missing)
end
end end
end end
end end

View file

@ -6,3 +6,7 @@ en:
For more information on the failure, enable detailed logging by setting For more information on the failure, enable detailed logging by setting
the environment variable VAGRANT_LOG to DEBUG. the environment variable VAGRANT_LOG to DEBUG.
lxc_template_file_missing: |-
The template file used for creating the container was not found for %{name}
box.

View file

@ -4,37 +4,41 @@ require 'vagrant-lxc/action/base_action'
require 'vagrant-lxc/action/handle_box_metadata' require 'vagrant-lxc/action/handle_box_metadata'
describe Vagrant::LXC::Action::HandleBoxMetadata do describe Vagrant::LXC::Action::HandleBoxMetadata do
let(:tar_cache) { 'template.zip' } let(:metadata) { {'template-opts' => {'--foo' => 'bar'}} }
let(:template_name) { 'ubuntu-lts' } let(:box) { mock(:box, name: 'box-name', metadata: metadata, directory: box_directory) }
let(:after_create) { 'setup-vagrant-user.sh' } let(:box_directory) { Pathname.new('/path/to/box') }
let(:metadata) { {'template-name' => template_name, 'tar-cache' => tar_cache, 'after-create-script' => after_create} }
let(:box) { mock(:box, name: 'box-name', metadata: metadata, directory: Pathname.new('/path/to/box')) }
let(:machine) { mock(:machine, box: box) } let(:machine) { mock(:machine, box: box) }
let(:app) { mock(:app, call: true) } let(:app) { mock(:app, call: true) }
let(:env) { {machine: machine} } let(:env) { {machine: machine} }
let(:tmpdir) { '/tmp/rootfs/dir' }
subject { described_class.new(app, env) } subject { described_class.new(app, env) }
before do before do
Dir.stub(mktmpdir: tmpdir)
File.stub(exists?: true)
subject.stub(:system) subject.stub(:system)
subject.call(env) subject.call(env)
end end
it 'sets box directory as lxc-cache-path' do it 'creates a tmp directory to store rootfs-cache-path' do
metadata['lxc-cache-path'].should == box.directory.to_s metadata['rootfs-cache-path'].should == tmpdir
end
it 'prepends box directory to after-create-script' do
metadata['after-create-script'].should == "#{box.directory.to_s}/#{after_create}"
end end
it 'prepends vagrant and box name to template-name' do it 'prepends vagrant and box name to template-name' do
metadata['template-name'].should == "vagrant-#{box.name}-#{template_name}" metadata['template-name'].should == "vagrant-#{box.name}"
end end
it 'copies box template file to the right folder' do it 'copies box template file to the right folder' do
src = box.directory.join(template_name).to_s src = box_directory.join('lxc-template').to_s
dest = "/usr/share/lxc/templates/lxc-#{metadata['template-name']}" dest = "/usr/share/lxc/templates/lxc-#{metadata['template-name']}"
subject.should have_received(:system).with("sudo su root -c \"cp #{src} #{dest}\"")
subject.should have_received(:system).
with("sudo su root -c \"cp #{src} #{dest}\"")
end
it 'extracts rootfs into a tmp folder' do
subject.should have_received(:system).
with(%Q[sudo su root -c "cd #{box_directory} && tar xfz rootfs.tar.gz -C #{tmpdir}"])
end end
end end

View file

@ -78,14 +78,13 @@ describe Vagrant::LXC::Container do
describe 'creation' do describe 'creation' do
let(:name) { 'random-container-name' } let(:name) { 'random-container-name' }
let(:template_name) { 'template-name' } let(:template_name) { 'template-name' }
let(:tar_cache_path) { '/path/to/tar/cache' } let(:rootfs_cache) { '/path/to/cache' }
let(:lxc_cache) { '/path/to/cache' }
let(:public_key_path) { Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s } let(:public_key_path) { Vagrant.source_root.join('keys', 'vagrant.pub').expand_path.to_s }
before do before do
subject.stub(lxc: true) subject.stub(lxc: true)
SecureRandom.stub(hex: name) SecureRandom.stub(hex: name)
subject.create 'template-name' => template_name, 'tar-cache' => tar_cache_path, 'lxc-cache-path' => lxc_cache subject.create 'template-name' => template_name, 'rootfs-cache-path' => rootfs_cache, 'template-opts' => { '--foo' => 'bar'}
end end
it 'calls lxc-create with the right arguments' do it 'calls lxc-create with the right arguments' do
@ -94,34 +93,13 @@ describe Vagrant::LXC::Container do
'--template', template_name, '--template', template_name,
'--name', name, '--name', name,
'--', '--',
'-S', public_key_path, '--auth-key', public_key_path,
'--cache-path', lxc_cache, '--cache', rootfs_cache,
'-T', tar_cache_path '--foo', 'bar'
) )
end end
end end
describe 'after create script execution' do
let(:name) { 'random-container-name' }
let(:after_create_path) { '/path/to/after/create' }
let(:execute_cmd) { @execute_cmd }
let(:priv_key_path) { Vagrant.source_root.join('keys', 'vagrant').expand_path.to_s }
let(:ip) { '10.0.3.234' }
before do
subject.stub(dhcp_ip: ip)
subject.stub(:execute) { |*args| @execute_cmd = args.join(' ') }
subject.run_after_create_script after_create_path
end
it 'runs after-create-script when present passing required variables' do
execute_cmd.should include after_create_path
execute_cmd.should include "-r /var/lib/lxc/#{name}/rootfs"
execute_cmd.should include "-k #{priv_key_path}"
execute_cmd.should include "-i #{ip}"
end
end
describe 'destruction' do describe 'destruction' do
let(:name) { 'container-name' } let(:name) { 'container-name' }

View file

@ -1,22 +1,17 @@
namespace :boxes do namespace :boxes do
namespace :build do namespace :quantal64 do
IMAGE_ROOT = 'https://cloud-images.ubuntu.com/releases/quantal/release-20130206' desc 'Build Ubuntu Quantal 64 bits Vagrant LXC box'
IMAGE_NAME = 'ubuntu-12.10-server-cloudimg-amd64-root.tar.gz' task :build do
def download(source, destination) if File.exists?('./boxes/output/lxc-quantal64.box')
destination = "#{File.dirname __FILE__}/../#{destination}" puts 'Box has been built already!'
if File.exists?(destination) exit 1
puts 'Skipping box image download'
else
sh "wget #{source} -O #{destination}"
end end
end
desc 'Packages an Ubuntu cloud image as a Vagrant LXC box'
task 'ubuntu-cloud' do
sh 'mkdir -p boxes/output' sh 'mkdir -p boxes/output'
download "#{IMAGE_ROOT}/#{IMAGE_NAME}", "boxes/ubuntu-cloud/#{IMAGE_NAME}" sh 'cd boxes/quantal64 && sudo ./download-ubuntu'
sh 'rm -f output/ubuntu-cloud.box' sh 'rm -f boxes/quantal64/rootfs.tar.gz'
sh 'cd boxes/ubuntu-cloud && tar -czf ../output/ubuntu-cloud.box ./*' sh 'cd boxes/quantal64 && sudo tar --numeric-owner -czf rootfs.tar.gz ./rootfs-amd64/*'
sh "cd boxes/quantal64 && sudo chown #{ENV['USER']}:#{ENV['USER']} rootfs.tar.gz && tar -czf ../output/lxc-quantal64.box ./* --exclude=rootfs-amd64 --exclude=download-ubuntu"
end end
end end
end end