]> git.ipfire.org Git - thirdparty/lxc.git/commitdiff
add a clone hook for ubuntu-cloud images
authorScott Moser <smoser@ubuntu.com>
Thu, 8 Aug 2013 18:16:59 +0000 (19:16 +0100)
committerSerge Hallyn <serge.hallyn@ubuntu.com>
Fri, 9 Aug 2013 13:40:25 +0000 (08:40 -0500)
This allows ability to now specify '--userdata' arguments to 'create' or
to 'clone'. So now, the following means very fast start of instances with
different user-data.

$ sudo lxc-create -t ubuntu-cloud -n precise -- \
   -r precise --arch amd64

$ sudo lxc-clone -B overlayfs -o precise -s -n ephem1 \
   --userdata="my.userdata1"
$ sudo lxc-clone -B overlayfs -o precise -s -n ephem2 \
   --userdata="my.userdata2"

Also present here is
 * an improvement to the static list of Ubuntu releases. It uses
   ubuntu-distro-info if available degrades back to a static list on failure.
 * moving of the replacement variables to the top of the create template This
   is just to make it more obvious what is being replaced and put them in a
   single location.

Signed-off-by: Scott Moser <smoser@ubuntu.com>
hooks/ubuntu-cloud-prep [new file with mode: 0755]
templates/lxc-ubuntu-cloud.in

diff --git a/hooks/ubuntu-cloud-prep b/hooks/ubuntu-cloud-prep
new file mode 100755 (executable)
index 0000000..d9a32c4
--- /dev/null
@@ -0,0 +1,162 @@
+#!/bin/bash
+## If the container being cloned has one or more lxc.hook.clone specified,
+## then the specified hooks will be called for the new  container.  The first
+## 3 arguments passed to the clone hook will be:
+##  1. the container name
+##  2. a section ('lxc')
+##  3. hook type ('clone')
+##  4. .. additional arguments to lxc-clone
+## Environment variables:
+##  LXC_ROOTFS_MOUNT: path under which the container's root fs is mounted.
+##  LXC_CONFIG_FILE:  The configuration file pathname
+##  LXC_SRC_NAME: old container name
+##  LXC_ROOTFS_PATH: path or device on which the root fs is located
+
+VERBOSITY=""
+
+error() { echo "$@" 1>&2; }
+debug() { [ "$1" -ge "$VERBOSITY" ] || return; shift; error "$@"; }
+fail() { [ $# -eq 0 ] || error "$@"; exit 1; }
+
+prep_usage() {
+cat <<EOF
+Usage: ${0##*/} [options] root-dir
+
+  root-dir is the root directory to operate on
+
+  [ -C | --cloud  ]:       do not configure a datasource.  incompatible with
+                           options marked '[ds]'
+  [ -i | --instance-id]:   instance-id for cloud-init, defaults to random [ds]
+  [ -L | --nolocales ]:    Do not copy host's locales into container
+  [ -S | --auth-key ]:     ssh public key file for datasource [ds]
+  [ -u | --userdata ]:     user-data file for cloud-init [ds]
+
+EOF
+}
+
+prep() {
+    local short_opts="Chi:L:S:u:v"
+    local long_opts="auth-key:,cloud,help,hostid:,name:,nolocales:,userdata:,verbose"
+    local getopt_out getopt_ret
+    getopt_out=$(getopt --name "${0##*/}" \
+        --options "${short_opts}" --long "${long_opts}" -- "$@" 2>/dev/null) ||
+        :
+    getopt_ret=$?
+    if [ $getopt_ret -eq 0 ]; then
+        eval set -- "${getopt_out}" ||
+        { error "Unexpected error reading usage"; return 1; }
+    fi
+
+    local cur="" next=""
+    local userdata="" hostid="" authkey="" locales=1 cloud=0 name=""
+    while [ $# -ne 0 ]; do
+        cur="$1"; next="$2";
+        case "$cur" in
+            -C|--cloud) cloud=1;;
+            -h|--help) prep_usage; return 0;;
+               --name) name="$next";;
+            -i|--hostid) hostid="$next";;
+            -L|--nolocales) locales=0;;
+            -S|--auth-key)
+                [ -f "$next" ] ||
+                    { error "--auth-key: '$next' not a file"; return 1; }
+                authkey="$next";;
+            -u|--userdata)
+                [ -f "$next" ] ||
+                    { error "--userdata: '$next' not a file"; return 1; }
+                userdata="$next";;
+            -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
+            --) shift; break;;
+        esac
+        shift;
+    done
+
+    [ $# -eq 1 ] || {
+        prep_usage 1>&2;
+        error "expected 1 arguments, got ($_LXC_HOOK) $#: $*";
+        return 1;
+    }
+
+    local root_d="$1";
+
+    if [ $getopt_ret -ne 0 -a "$_LXC_HOOK" = "clone" ]; then
+        # getopt above failed, but we were called from lxc clone. there might
+        # be multiple clone hooks and the args provided here not for us.  This
+        # seems like not the greatest interface, so all we'll do is mention it.
+        error "${0##*}: usage failed, continuing with defaults"
+    fi
+
+    local seed_d=""
+    seed_d="$root_d/var/lib/cloud/seed/nocloud-net"
+    if [ $cloud -eq 1 ]; then
+        debug 1 "--cloud provided, not modifying seed in '$seed_d'"
+    else
+        if [ -z "$hostid" ]; then
+            hostid=$(uuidgen | cut -c -8) && [ -n "$hostid" ] ||
+                { error "failed to get hostid"; return 1; }
+        fi
+        mkdir -p "$seed_d" ||
+            { error "failed to create '$seed_d'"; return 1; }
+
+        echo "instance-id: lxc-$hostid" > "$seed_d/meta-data" ||
+            { error "failed to write to $seed_d/meta-data"; return 1; }
+
+        if [ -n "$authkey" ]; then
+            {
+                echo "public-keys:" &&
+                sed -e '/^$/d' -e 's,^,- ,' "$authkey"
+            } >> "$seed_d/meta-data"
+            [ $? -eq 0 ] ||
+                { error "failed to write public keys to metadata"; return 1; }
+        fi
+
+        local larch="usr/lib/locale/locale-archive"
+        if [ $locales -eq 1 ]; then
+            cp "/$larch" "$root_d/$larch" || {
+                error "failed to cp '/$larch' '$root_d/$larch'";
+                return 1;
+            }
+        fi
+
+        if [ -z "$MIRROR" ]; then
+            MIRROR="http://archive.ubuntu.com/ubuntu"
+        fi
+
+        {
+            local lc=$(locale | awk -F= '/LANG=/ {print $NF; }')
+            echo "#cloud-config"
+            echo "output: {all: '| tee -a /var/log/cloud-init-output.log'}"
+            echo "apt_mirror: $MIRROR"
+            echo "manage_etc_hosts: localhost"
+            [ -z "$LANG" ] || echo "locale: $LANG";
+            echo "password: ubuntu"
+            echo "chpasswd: { expire: false; }"
+            
+        } > "$seed_d/user-data"
+        [ $? -eq 0 ] || {
+            error "failed to write user-data write to '$seed_d/user-data'";
+            return 1;
+        }
+    fi
+
+}
+
+main() {
+    # main just joins 2 modes of being called.  from user one from lxc clone
+    local _LXC_HOOK
+    if [ -n "$LXC_ROOTFS_MOUNT" -a "$3" = "clone" ]; then
+        _LXC_HOOK="clone"
+        local name="$1"
+        shift 3
+        debug 1 prep "--name=$name" "$LXC_ROOTFS_MOUNT" "$@"
+        prep "--name=$name" "$LXC_ROOTFS_MOUNT" "$@"
+    else
+        _LXC_HOOK=""
+        prep "$@"
+    fi
+    return $?
+}
+
+main "$@"
+
+# vi: ts=4 expandtab
index 25be52e0aeb86b488ac32931c77cf12d4fb8ddf7..81720f5e175e3315a7384dcd82f28476115ac23b 100644 (file)
@@ -1,7 +1,7 @@
 #!/bin/bash
 
-# template script for generating ubuntu container for LXC based on released cloud
-# images
+# template script for generating ubuntu container for LXC based on released
+# cloud images.
 #
 # Copyright © 2012 Serge Hallyn <serge.hallyn@canonical.com>
 #
 
 set -e
 
+STATE_DIR="@LOCALSTATEDIR@"
+HOOK_DIR="@LXCHOOKDIR@"
+CLONE_HOOK_FN="$HOOK_DIR/ubuntu-cloud-prep"
+
 if [ -r /etc/default/lxc ]; then
     . /etc/default/lxc
 fi
@@ -75,6 +79,8 @@ lxc.cap.drop = sys_module mac_admin mac_override sys_time
 #lxc.aa_profile = lxc-container-default-with-nesting
 #lxc.hook.mount = /usr/share/lxc/hooks/mountcgroups
 
+lxc.hook.clone = ${CLONE_HOOK_FN}
+
 lxc.cgroup.devices.deny = a
 # Allow any mknod (but not using the node)
 lxc.cgroup.devices.allow = c *:* m
@@ -143,17 +149,13 @@ Generic Options
 [ -r | --release <release> ]: Release name of container, defaults to host
 [ --rootfs <path> ]: Path in which rootfs will be placed
 [ -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'
 
-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
-
+Additionally, clone hooks can be passed through (ie, --userdata).  For those,
+see:
+  $CLONE_HOOK_FN --help
 EOF
     return 0
 }
@@ -165,14 +167,15 @@ if [ $? -ne 0 ]; then
 fi
 eval set -- "$options"
 
-release=lucid
+# default release is precise, or the systems release if recognized
+release=precise
 if [ -f /etc/lsb-release ]; then
     . /etc/lsb-release
-    case "$DISTRIB_CODENAME" in
-        lucid|natty|oneiric|precise|quantal)
-            release=$DISTRIB_CODENAME
-        ;;
-    esac
+    rels=$(ubuntu-distro-info --supported 2>/dev/null) ||
+        rels="lucid natty oneiric precise quantal raring saucy"
+    for r in $rels; do
+        [ "$DISTRIB_CODENAME" = "$r" ] && release="$r"
+    done
 fi
 
 # Code taken from debootstrap
@@ -201,6 +204,7 @@ cloud=0
 locales=1
 flushcache=0
 stream="released"
+cloneargs=()
 while true
 do
     case "$1" in
@@ -210,20 +214,22 @@ do
     -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;;
-    --rootfs)       rootfs=$2; shift 2;;
+    --rootfs)          rootfs=$2; shift 2;;
+    -L|--no?locales)   cloneargs[${#cloneargs[@]}]="--no-locales"; shift 1;;
+    -i|--hostid)       cloneargs[${#cloneargs[@]}]="--hostid=$2"; shift 2;;
+    -u|--userdata)     cloneargs[${#cloneargs[@]}]="--userdata=$2"; shift 2;;
+    -C|--cloud)        cloneargs[${#cloneargs[@]}]="--cloud"; shift 1;;
+    -S|--auth-key)     cloneargs[${#cloneargs[@]}]="--auth-key=$2"; shift 2;;
     --)                shift 1; break ;;
         *)              break ;;
     esac
 done
 
+cloneargs=( "--name=$name" "${cloneargs[@]}" )
+
 if [ $debug -eq 1 ]; then
     set -x
 fi
@@ -263,24 +269,6 @@ if [ "$stream" != "daily" -a "$stream" != "released" ]; then
     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
@@ -306,7 +294,7 @@ type wget
 
 # determine the url, tarball, and directory names
 # download if needed
-cache="@LOCALSTATEDIR@/cache/lxc/cloud-$release"
+cache="$STATE_DIR/cache/lxc/cloud-$release"
 
 mkdir -p $cache
 
@@ -383,74 +371,22 @@ do_extract_rootfs() {
     mkdir -p $rootfs
     cd $rootfs
     tar -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
 }
 
 if [ -n "$tarball" ]; then
     do_extract_rootfs
 else
-    mkdir -p @LOCALSTATEDIR@/lock/subsys/
+    mkdir -p "$STATE_DIR/lock/subsys/"
     (
         flock -x 200
         do_extract_rootfs
-    ) 200>@LOCALSTATEDIR@/lock/subsys/lxc-ubuntu-cloud
+    ) 200>"$STATE_DIR/lock/subsys/lxc-ubuntu-cloud"
 fi
 
 copy_configuration $path $rootfs $name $arch $release
 
+"$CLONE_HOOK_FN" "${cloneargs[@]}" "$rootfs"
+
 echo "Container $name created."
 exit 0