From: Michael Prokop Date: Sun, 20 Apr 2014 00:31:00 +0000 (+0200) Subject: Initial packer/vagrant/autotest setup X-Git-Tag: v0.65~3 X-Git-Url: https://git.grml.org/?p=grml-debootstrap.git;a=commitdiff_plain;h=7fc0e06e6bb818dd8410e8dc2341da015121203a;hp=1081c44c4f6368490481c4c5b48281f3846cbed1 Initial packer/vagrant/autotest setup --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..74c16de --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.box +packer_cache +*_output diff --git a/Makefile b/Makefile index 364ebff..70cca32 100644 --- a/Makefile +++ b/Makefile @@ -24,3 +24,9 @@ online: all clean: rm -rf grml-debootstrap.8.html grml-debootstrap.8.xml grml-debootstrap.8 html-stamp man-stamp + +testrun: + cd ./packer && $(MAKE) compile && $(MAKE) packer + +vagrant: + vagrant up diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 0000000..a14cc93 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,122 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| + # All Vagrant configuration is done here. The most common configuration + # options are documented and commented below. For a complete reference, + # please see the online documentation at vagrantup.com. + + # Every Vagrant virtual environment requires a box to build off of. + config.vm.box = "packer/debian64_wheezy.box" + + # Disable automatic box update checking. If you disable this, then + # boxes will only be checked for updates when the user runs + # `vagrant box outdated`. This is not recommended. + # config.vm.box_check_update = false + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + # config.vm.network "forwarded_port", guest: 80, host: 8080 + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + # config.vm.network "private_network", ip: "192.168.33.10" + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network "public_network" + + # If true, then any SSH connections made will enable agent forwarding. + # Default value: false + # config.ssh.forward_agent = true + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + config.vm.provider "virtualbox" do |vb| + # Don't boot with headless mode + vb.gui = true + + # Use VBoxManage to customize the VM. For example to change memory: + # vb.customize ["modifyvm", :id, "--memory", "1024"] + end + # + # View the documentation for the provider you're using for more + # information on available options. + + # Enable provisioning with CFEngine. CFEngine Community packages are + # automatically installed. For example, configure the host as a + # policy server and optionally a policy file to run: + # + # config.vm.provision "cfengine" do |cf| + # cf.am_policy_hub = true + # # cf.run_file = "motd.cf" + # end + # + # You can also configure and bootstrap a client to an existing + # policy server: + # + # config.vm.provision "cfengine" do |cf| + # cf.policy_server_address = "10.0.2.15" + # end + + # Enable provisioning with Puppet stand alone. Puppet manifests + # are contained in a directory path relative to this Vagrantfile. + # You will need to create the manifests directory and a manifest in + # the file default.pp in the manifests_path directory. + # + # config.vm.provision "puppet" do |puppet| + # puppet.manifests_path = "manifests" + # puppet.manifest_file = "site.pp" + # end + + # Enable provisioning with chef solo, specifying a cookbooks path, roles + # path, and data_bags path (all relative to this Vagrantfile), and adding + # some recipes and/or roles. + # + # config.vm.provision "chef_solo" do |chef| + # chef.cookbooks_path = "../my-recipes/cookbooks" + # chef.roles_path = "../my-recipes/roles" + # chef.data_bags_path = "../my-recipes/data_bags" + # chef.add_recipe "mysql" + # chef.add_role "web" + # + # # You may also specify custom JSON attributes: + # chef.json = { :mysql_password => "foo" } + # end + + # Enable provisioning with chef server, specifying the chef server URL, + # and the path to the validation key (relative to this Vagrantfile). + # + # The Opscode Platform uses HTTPS. Substitute your organization for + # ORGNAME in the URL and validation key. + # + # If you have your own Chef Server, use the appropriate URL, which may be + # HTTP instead of HTTPS depending on your configuration. Also change the + # validation key to validation.pem. + # + # config.vm.provision "chef_client" do |chef| + # chef.chef_server_url = "https://api.opscode.com/organizations/ORGNAME" + # chef.validation_key_path = "ORGNAME-validator.pem" + # end + # + # If you're using the Opscode platform, your validator client is + # ORGNAME-validator, replacing ORGNAME with your organization name. + # + # If you have your own Chef Server, the default validation client name is + # chef-validator, unless you changed the configuration. + # + # chef.validation_client_name = "ORGNAME-validator" +end diff --git a/packer/Makefile b/packer/Makefile new file mode 100644 index 0000000..42785b0 --- /dev/null +++ b/packer/Makefile @@ -0,0 +1,5 @@ +compile: + gcc -shared -fPIC -ldl fake-uname.c -o fake-uname.so + +packer: + packer build debian64_wheezy.json diff --git a/packer/README.md b/packer/README.md new file mode 100644 index 0000000..119a97f --- /dev/null +++ b/packer/README.md @@ -0,0 +1,30 @@ +Creating Vagrant baseboxes and testing grml-debootstrap +======================================================= + +Purpose +------- + +This directory provides configurations and scripts to + +* test grml-debootstrap +* generate base.box for Virtualbox usage with Vagrant + +Required software +----------------- + +* [Packer](http://packer.io/) binary in $PATH +* [Vagrant](http://vagrantup.com/) +* [Virtualbox](https://www.virtualbox.org/) + +Usage instructions +------------------ + +To create a Debian base box for usage with Vagrant +(and while at it run the grml-debootstrap tests): + + % cd grml-debootstrap.git + % make testrun + +Start resulting Debian system via Vagrant: + + % vagrant up diff --git a/packer/debian64.bats b/packer/debian64.bats new file mode 100644 index 0000000..a677168 --- /dev/null +++ b/packer/debian64.bats @@ -0,0 +1,95 @@ +#!/usr/bin/env bats + +# config +mountpath="/mnt" +device="/dev/sda" +disk="${device}1" + +setup() { + mountpoint "$mountpath" &>/dev/null || mount "$disk" "$mountpath" +} + +teardown() { + mountpoint "$mountpath" &>/dev/null && umount "$mountpath" +} + +# tests +@test "debian_version exists" { + run cat "${mountpath}/etc/debian_version" + [ "$status" -eq 0 ] + [[ "$output" == [0-9].[0-9] ]] +} + +@test "kernel exists" { + run ls "${mountpath}"/boot/vmlinuz-* + [ "$status" -eq 0 ] + [[ "$output" =~ ${mountpath}/boot/vmlinuz-* ]] +} + +@test "initrd exists" { + run ls "${mountpath}"/boot/initrd.img-* + [ "$status" -eq 0 ] + [[ "$output" =~ ${mountpath}/boot/initrd.img-* ]] +} + +@test "grub-pc installed" { + run chroot $mountpath dpkg-query --show --showformat='${Status}' grub-pc + [ "$status" -eq 0 ] + [[ "$output" == "install ok installed" ]] +} + +@test "ext3/ext4 filesystem" { + fstype=$(blkid -o udev ${disk} | grep '^ID_FS_TYPE=') + run echo $fstype + [ "$status" -eq 0 ] + [[ $output =~ ID_FS_TYPE=ext[34] ]] +} + +@test "partition table" { + table_info=$(parted -s ${device} 'unit s print' | grep -A1 '^Number.*Start.*End' | tail -1) + regex='1 2048s.*primary ext[34] boot' + run echo $table_info + echo "debug: table_info = $table_info" + echo "debug: output = $output" + [[ $output =~ $regex ]] +} + +@test "tune2fs mount count setting" { + mount_count=$(tune2fs -l "$disk" | grep "^Maximum mount count:") + run echo "$mount_count" + [[ "$output" == "Maximum mount count: -1" ]] +} + +@test "kernel entry in grub config" { + run grep "Debian GNU/Linux, with Linux" "${mountpath}/boot/grub/grub.cfg" + [ "$status" -eq 0 ] +} + +@test "vim package is installed" { + run chroot "$mountpath" dpkg --list vim + [ "$status" -eq 0 ] +} + +@test "home directory for user vagrant" { + run ls -d "$mountpath"/home/vagrant + [ "$status" -eq 0 ] +} + +@test "home directory for user vagrant" { + run grep -q ssh-rsa "$mountpath"/home/vagrant/.ssh/authorized_keys + [ "$status" -eq 0 ] +} + +@test "sudo setup for user vagrant" { + run grep -q '^vagrant ALL=(ALL) NOPASSWD: ALL' "${mountpath}/etc/sudoers.d/vagrant" + [ "$status" -eq 0 ] +} + +@test "check for GRUB in MBR" { + regex='^00000180.*GRUB.*' + grub_string=$(dd if=${device} bs=512 count=1 2>/dev/null | hexdump -C | egrep "$regex") + run echo "$grub_string" + echo "debug: grub_string = $grub_string" + echo "debug: output = $output" + [[ $output =~ $regex ]] +} diff --git a/packer/debian64_provision.sh b/packer/debian64_provision.sh new file mode 100644 index 0000000..7830ad4 --- /dev/null +++ b/packer/debian64_provision.sh @@ -0,0 +1,132 @@ +#!/bin/bash + +set -e + +[ -f /etc/grml_cd ] || { echo "File /etc/grml_cd doesn't exist, not executing script to avoid data loss." >&2 ; exit 1 ; } + +TARGET=/mnt + +# if we notice an error then do NOT immediately return but provide +# user a chance to debug the VM +bailout() { + echo "* Noticed problem during execution, sleeping for 9999 seconds to provide debugging option" + sleep 9999 + echo "* Finally exiting with return code 1" + exit 1 +} +trap bailout ERR + +echo "* Executing automated partition setup" +cat > /tmp/partition_setup.txt << EOF +disk_config sda disklabel:msdos bootable:1 +primary / 800M- ext4 rw +EOF + +export LOGDIR='/tmp/setup-storage' +mkdir -p $LOGDIR + +export disklist=$(/usr/lib/fai/fai-disk-info | sort) +PATH=/usr/lib/fai:${PATH} setup-storage -f /tmp/partition_setup.txt -X + +echo "* Making sure we use latest grml-debootstrap version" +apt-get update +apt-get -y install grml-debootstrap + +# TODO - support testing the version provided by the ISO (without upgrading) +if [ -r /tmp/grml-debootstrap ] ; then + echo "* Found /tmp/grml-debootstrap - considering for usage as main grml-debootstrap script" + GRML_DEBOOTSTRAP="bash /tmp/grml-debootstrap" +else + GRML_DEBOOTSTRAP=grml-debootstrap +fi + +echo "* Installing Debian" +$GRML_DEBOOTSTRAP --hostname wheezy --release wheezy --target /dev/sda1 --grub /dev/sda --password grml --force 2>&1 | tee -a /tmp/grml-debootstrap.log + +echo "* Mounting target system" +mount /dev/sda1 ${TARGET} + +echo "* Installing make + gcc packages for Virtualbox Guest Additions" +chroot ${TARGET} apt-get -y install make gcc dkms + +echo "* Installing Virtualbox Guest Additions" +isofile="${HOME}/VBoxGuestAdditions.iso" + +KERNELHEADERS=$(basename $(find $TARGET/usr/src/ -maxdepth 1 -name linux-headers\* ! -name \*common) | sort -u -r -V | head -1) +if [ -z "$KERNELHEADERS" ] ; then + echo "Error: no kernel headers found for building the VirtualBox Guest Additions kernel module." >&2 + exit 1 +fi + +KERNELVERSION=${KERNELHEADERS##linux-headers-} +if [ -z "$KERNELVERSION" ] ; then + echo "Error: no kernel version could be identified." >&2 + exit 1 +fi + +cp /tmp/fake-uname.so "${TARGET}/tmp/fake-uname.so" +mkdir -p "${TARGET}/media/cdrom" +mountpoint "${TARGET}/media/cdrom" >/dev/null && umount "${TARGET}/media/cdrom" +mount -t iso9660 $isofile "${TARGET}/media/cdrom/" +UTS_RELEASE=$KERNELVERSION LD_PRELOAD=/tmp/fake-uname.so grml-chroot "$TARGET" /media/cdrom/VBoxLinuxAdditions.run --nox11 || true +tail -10 "${TARGET}/var/log/VBoxGuestAdditions.log" +umount "${TARGET}/media/cdrom/" + +# work around regression in virtualbox-guest-additions-iso 4.3.10 +if [ -d ${TARGET}/opt/VBoxGuestAdditions-4.3.10 ] ; then + ln -s /opt/VBoxGuestAdditions-4.3.10/lib/VBoxGuestAdditions ${TARGET}/usr/lib/VBoxGuestAdditions +fi + +echo "* Setting password for user root to 'vagrant'" +echo root:vagrant | chroot ${TARGET} chpasswd + +echo "* Installing sudo package" +chroot ${TARGET} apt-get -y install sudo + +echo "* Adding Vagrant user" +chroot ${TARGET} useradd -d /home/vagrant -m -u 1000 vagrant + +echo "* Installing Vagrant ssh key" +mkdir -m 0700 -p ${TARGET}/home/vagrant/.ssh +echo "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key" >> ${TARGET}/home/vagrant/.ssh/authorized_keys +chmod 0600 ${TARGET}/home/vagrant/.ssh/authorized_keys +chroot ${TARGET} chown vagrant:vagrant /home/vagrant/.ssh /home/vagrant/.ssh/authorized_keys + +echo "* Setting up sudo configuration for user vagrant" +echo "vagrant ALL=(ALL) NOPASSWD: ALL" > ${TARGET}/etc/sudoers.d/vagrant + +if [ -f ${TARGET}/etc/ssh/sshd_config ] && ! grep -q '^UseDNS' ${TARGET}/etc/ssh/sshd_config ; then + echo "* Disabling UseDNS in sshd config" + echo "UseDNS no" >> ${TARGET}/etc/ssh/sshd_config +fi + +echo "* Cleaning up apt stuff" +chroot ${TARGET} apt-get clean +rm -f ${TARGET}/var/lib/apt/lists/*Packages \ + ${TARGET}/var/lib/apt/lists/*Release \ + ${TARGET}/var/lib/apt/lists/*Sources \ + ${TARGET}/var/lib/apt/lists/*Index* \ + ${TARGET}/var/lib/apt/lists/*Translation* \ + ${TARGET}/var/lib/apt/lists/*.gpg \ + ${TARGET}/var/cache/apt-show-versions/* \ + ${TARGET}/var/cache/debconf/*.dat-old \ + ${TARGET}/var/cache/apt/*.bin \ + ${TARGET}/var/lib/aptitude/pkgstates.old + +echo "* Unmounting target system" +umount ${TARGET} + +echo "* Checking for bats" +if dpkg --list bats >/dev/null 2>&1 ; then + echo "* bats is already present, nothing to do." +else + echo "* Installing bats" + apt-get update + apt-get -y install bats + # dpkg -i /tmp/bats*deb +fi + +echo "* Running tests to verify grml-debootstrap system" +bats /tmp/debian64.bats -t + +echo "* Finished execution of $0" diff --git a/packer/debian64_wheezy.json b/packer/debian64_wheezy.json new file mode 100644 index 0000000..80de5bd --- /dev/null +++ b/packer/debian64_wheezy.json @@ -0,0 +1,63 @@ +{ + "variables": { + "grml_type": "grml64-full", + "grml_version": "2014.03", + "grml_iso_checksum": "1d3a0adfa3d395746e9e7f8748faec536f5c7b84" + }, + + "builders": [ + { + "type": "virtualbox-iso", + "vm_name": "{{ user `grml_type` }}_{{ user `grml_version` }}", + "guest_os_type": "Debian_64", + "hard_drive_interface": "sata", + "iso_url": "http://download.grml.org/{{ user `grml_type` }}_{{ user `grml_version` }}.iso", + "iso_checksum": "{{ user `grml_iso_checksum` }}", + "iso_checksum_type": "sha1", + "ssh_wait_timeout": "20m", + "ssh_username": "root", + "ssh_password": "grml", + "output_directory": "{{ user `grml_type` }}_output", + "headless": false, + "boot_wait": "5s", + "boot_command": [ + " ssh=grml " + ], + "shutdown_command": "shutdown -h now" + } + ], + "provisioners": [ + { + "type": "file", + "source": "fake-uname.so", + "destination": "/tmp/fake-uname.so" + }, + { + "type": "file", + "source": "debian64.bats", + "destination": "/tmp/debian64.bats" + }, + { + "type": "file", + "source": "../grml-debootstrap", + "destination": "/tmp/grml-debootstrap" + }, + { + "type": "shell", + "scripts": [ + "debian64_provision.sh" + ] + } + ], + "post-processors": [ + { + "type": "vagrant", + "compression_level": 5, + "override": { + "virtualbox": { + "output": "debian64_wheezy.box" + } + } + } + ] +} diff --git a/packer/fake-uname.c b/packer/fake-uname.c new file mode 100644 index 0000000..7bbcb24 --- /dev/null +++ b/packer/fake-uname.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef UTS_RELEASE +#define UTS_RELEASE "0.0.0" +#endif + +#ifndef RTLD_NEXT +#define RTLD_NEXT ((void *) -1l) +#endif + +typedef int (*uname_t) (struct utsname * buf); + +static void *get_libc_func(const char *funcname) +{ + void *func; + char *error; + + func = dlsym(RTLD_NEXT, funcname); + if ((error = dlerror()) != NULL) { + fprintf(stderr, "Can't locate libc function `%s' error: %s", funcname, error); + _exit(EXIT_FAILURE); + } + return func; +} + +int uname(struct utsname *buf) +{ + int ret; + char *env = NULL; + uname_t real_uname = (uname_t) get_libc_func("uname"); + + ret = real_uname((struct utsname *) buf); + strncpy(buf->release, ((env = getenv("UTS_RELEASE")) == NULL) ? UTS_RELEASE : env, 65); + return ret; +} diff --git a/packer/fake-uname.so b/packer/fake-uname.so new file mode 100755 index 0000000..1952279 Binary files /dev/null and b/packer/fake-uname.so differ