-----BEGIN PGP SIGNATURE-----

Version: GnuPG v2
 
 iQEcBAABCAAGBQJXj15cAAoJEMo1YkxqkXHGkwIIAIgXZ7ciQNS6HK8WWlfvulfh
 gFnu32HDNih3zYk6N5NNcpHxi16dYLdj98WteWlkYUwwJ2iQBH8e0VJPVMYzJC+g
 pdbaUjXScpCkumA+vH6PgUjgJwH3Z1FMj+r9I1ZF6POy17DjOy6xmCCr+Pvh0sxm
 NfRzgnUM1nsHvfVS6WM+NorlmEX/wvkWw/qBjv49N5hoJw9I0saJopNM+oh6+Pgy
 A87DM83O0a8fHPBoPV7L6TDYasNl/Y26iCliBu9qxW/pGODjw8ohrQkxq8Bopo08
 jy7ITHNfrcK44PFMmCZELbygtLZe5eB5qmHndyAGQhDjrHpe+/cv84ar/Dh+MrY=
 =PXt6
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/famz/tags/docker-pull-request' into staging

# gpg: Signature made Wed 20 Jul 2016 12:19:56 BST
# gpg:                using RSA key 0xCA35624C6A9171C6
# gpg: Good signature from "Fam Zheng <famz@redhat.com>"
# gpg: WARNING: This key is not certified with a trusted signature!
# gpg:          There is no indication that the signature belongs to the owner.
# Primary key fingerprint: 5003 7CB7 9706 0F76 F021  AD56 CA35 624C 6A91 71C6

* remotes/famz/tags/docker-pull-request:
  docker: pass EXECUTABLE to build script
  docker: Don't start a container that doesn't exist
  docker: Add "images" subcommand to docker.py
  docker: Fix exit code if $CMD failed
  docker: More sensible run script
  tests/docker/docker.py: add update operation
  tests/docker/dockerfiles: new debian-bootstrap.docker
  tests/docker/docker.py: check and run .pre script
  tests/docker/docker.py: support --include-executable
  tests/docker/docker.py: docker_dir outside build

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2016-07-20 18:52:10 +01:00
commit 46ca418d9f
5 changed files with 280 additions and 17 deletions

View File

@ -46,7 +46,8 @@ docker-image: ${DOCKER_TARGETS}
docker-image-%: $(DOCKER_FILES_DIR)/%.docker docker-image-%: $(DOCKER_FILES_DIR)/%.docker
$(call quiet-command,\ $(call quiet-command,\
$(SRC_PATH)/tests/docker/docker.py build qemu:$* $< \ $(SRC_PATH)/tests/docker/docker.py build qemu:$* $< \
$(if $V,,--quiet) $(if $(NOCACHE),--no-cache),\ $(if $V,,--quiet) $(if $(NOCACHE),--no-cache) \
$(if $(EXECUTABLE),--include-executable=$(EXECUTABLE)),\
" BUILD $*") " BUILD $*")
# Expand all the pre-requistes for each docker image and test combination # Expand all the pre-requistes for each docker image and test combination
@ -95,6 +96,7 @@ docker:
@echo ' DEBUG=1 Stop and drop to shell in the created container' @echo ' DEBUG=1 Stop and drop to shell in the created container'
@echo ' before running the command.' @echo ' before running the command.'
@echo ' NOCACHE=1 Ignore cache when build images.' @echo ' NOCACHE=1 Ignore cache when build images.'
@echo ' EXECUTABLE=<path> Include executable in image.'
docker-run-%: CMD = $(shell echo '$@' | sed -e 's/docker-run-\([^@]*\)@\(.*\)/\1/') docker-run-%: CMD = $(shell echo '$@' | sed -e 's/docker-run-\([^@]*\)@\(.*\)/\1/')
docker-run-%: IMAGE = $(shell echo '$@' | sed -e 's/docker-run-\([^@]*\)@\(.*\)/\2/') docker-run-%: IMAGE = $(shell echo '$@' | sed -e 's/docker-run-\([^@]*\)@\(.*\)/\2/')
@ -105,7 +107,10 @@ docker-run-%: docker-qemu-src
fi fi
$(if $(filter $(TESTS),$(CMD)),$(if $(filter $(IMAGES),$(IMAGE)), \ $(if $(filter $(TESTS),$(CMD)),$(if $(filter $(IMAGES),$(IMAGE)), \
$(call quiet-command,\ $(call quiet-command,\
$(SRC_PATH)/tests/docker/docker.py run $(if $V,,--rm) \ if $(SRC_PATH)/tests/docker/docker.py images \
--format={{.Repository}}:{{.Tag}} | \
grep -qx qemu:$(IMAGE); then \
$(SRC_PATH)/tests/docker/docker.py run $(if $V,,--rm) \
-t \ -t \
$(if $(DEBUG),-i,--net=none) \ $(if $(DEBUG),-i,--net=none) \
-e TARGET_LIST=$(TARGET_LIST) \ -e TARGET_LIST=$(TARGET_LIST) \
@ -114,11 +119,10 @@ docker-run-%: docker-qemu-src
-e CCACHE_DIR=/var/tmp/ccache \ -e CCACHE_DIR=/var/tmp/ccache \
-v $$(realpath $(DOCKER_SRC_COPY)):/var/tmp/qemu:z$(COMMA)ro \ -v $$(realpath $(DOCKER_SRC_COPY)):/var/tmp/qemu:z$(COMMA)ro \
-v $(DOCKER_CCACHE_DIR):/var/tmp/ccache:z \ -v $(DOCKER_CCACHE_DIR):/var/tmp/ccache:z \
-w /var/tmp/qemu \
qemu:$(IMAGE) \ qemu:$(IMAGE) \
$(if $V,/bin/bash -x ,) \ /var/tmp/qemu/run \
./run \
$(CMD); \ $(CMD); \
fi \
, " RUN $(CMD) in $(IMAGE)"))) , " RUN $(CMD) in $(IMAGE)")))
docker-clean: docker-clean:

View File

@ -20,7 +20,10 @@ import atexit
import uuid import uuid
import argparse import argparse
import tempfile import tempfile
from shutil import copy import re
from tarfile import TarFile, TarInfo
from StringIO import StringIO
from shutil import copy, rmtree
def _text_checksum(text): def _text_checksum(text):
"""Calculate a digest string unique to the text content""" """Calculate a digest string unique to the text content"""
@ -38,6 +41,54 @@ def _guess_docker_command():
raise Exception("Cannot find working docker command. Tried:\n%s" % \ raise Exception("Cannot find working docker command. Tried:\n%s" % \
commands_txt) commands_txt)
def _copy_with_mkdir(src, root_dir, sub_path):
"""Copy src into root_dir, creating sub_path as needed."""
dest_dir = os.path.normpath("%s/%s" % (root_dir, sub_path))
try:
os.makedirs(dest_dir)
except OSError:
# we can safely ignore already created directories
pass
dest_file = "%s/%s" % (dest_dir, os.path.basename(src))
copy(src, dest_file)
def _get_so_libs(executable):
"""Return a list of libraries associated with an executable.
The paths may be symbolic links which would need to be resolved to
ensure theright data is copied."""
libs = []
ldd_re = re.compile(r"(/.*/)(\S*)")
try:
ldd_output = subprocess.check_output(["ldd", executable])
for line in ldd_output.split("\n"):
search = ldd_re.search(line)
if search and len(search.groups()) == 2:
so_path = search.groups()[0]
so_lib = search.groups()[1]
libs.append("%s/%s" % (so_path, so_lib))
except subprocess.CalledProcessError:
print "%s had no associated libraries (static build?)" % (executable)
return libs
def _copy_binary_with_libs(src, dest_dir):
"""Copy a binary executable and all its dependant libraries.
This does rely on the host file-system being fairly multi-arch
aware so the file don't clash with the guests layout."""
_copy_with_mkdir(src, dest_dir, "/usr/bin")
libs = _get_so_libs(src)
if libs:
for l in libs:
so_path = os.path.dirname(l)
_copy_with_mkdir(l , dest_dir, so_path)
class Docker(object): class Docker(object):
""" Running Docker commands """ """ Running Docker commands """
def __init__(self): def __init__(self):
@ -45,9 +96,11 @@ class Docker(object):
self._instances = [] self._instances = []
atexit.register(self._kill_instances) atexit.register(self._kill_instances)
def _do(self, cmd, quiet=True, **kwargs): def _do(self, cmd, quiet=True, infile=None, **kwargs):
if quiet: if quiet:
kwargs["stdout"] = subprocess.PIPE kwargs["stdout"] = subprocess.PIPE
if infile:
kwargs["stdin"] = infile
return subprocess.call(self._command + cmd, **kwargs) return subprocess.call(self._command + cmd, **kwargs)
def _do_kill_instances(self, only_known, only_active=True): def _do_kill_instances(self, only_known, only_active=True):
@ -87,22 +140,27 @@ class Docker(object):
labels = json.loads(resp)[0]["Config"].get("Labels", {}) labels = json.loads(resp)[0]["Config"].get("Labels", {})
return labels.get("com.qemu.dockerfile-checksum", "") return labels.get("com.qemu.dockerfile-checksum", "")
def build_image(self, tag, dockerfile, df_path, quiet=True, argv=None): def build_image(self, tag, docker_dir, dockerfile, quiet=True, argv=None):
if argv == None: if argv == None:
argv = [] argv = []
tmp_dir = tempfile.mkdtemp(prefix="docker_build")
tmp_df = tempfile.NamedTemporaryFile(dir=tmp_dir, suffix=".docker") tmp_df = tempfile.NamedTemporaryFile(dir=docker_dir, suffix=".docker")
tmp_df.write(dockerfile) tmp_df.write(dockerfile)
tmp_df.write("\n") tmp_df.write("\n")
tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s" % tmp_df.write("LABEL com.qemu.dockerfile-checksum=%s" %
_text_checksum(dockerfile)) _text_checksum(dockerfile))
tmp_df.flush() tmp_df.flush()
self._do(["build", "-t", tag, "-f", tmp_df.name] + argv + \ self._do(["build", "-t", tag, "-f", tmp_df.name] + argv + \
[tmp_dir], [docker_dir],
quiet=quiet) quiet=quiet)
def update_image(self, tag, tarball, quiet=True):
"Update a tagged image using "
self._do(["build", "-t", tag, "-"], quiet=quiet, infile=tarball)
def image_matches_dockerfile(self, tag, dockerfile): def image_matches_dockerfile(self, tag, dockerfile):
try: try:
checksum = self.get_image_dockerfile_checksum(tag) checksum = self.get_image_dockerfile_checksum(tag)
@ -121,6 +179,9 @@ class Docker(object):
self._instances.remove(label) self._instances.remove(label)
return ret return ret
def command(self, cmd, argv, quiet):
return self._do([cmd] + argv, quiet=quiet)
class SubCommand(object): class SubCommand(object):
"""A SubCommand template base class""" """A SubCommand template base class"""
name = None # Subcommand name name = None # Subcommand name
@ -151,6 +212,10 @@ class BuildCommand(SubCommand):
""" Build docker image out of a dockerfile. Arguments: <tag> <dockerfile>""" """ Build docker image out of a dockerfile. Arguments: <tag> <dockerfile>"""
name = "build" name = "build"
def args(self, parser): def args(self, parser):
parser.add_argument("--include-executable", "-e",
help="""Specify a binary that will be copied to the
container together with all its dependent
libraries""")
parser.add_argument("tag", parser.add_argument("tag",
help="Image Tag") help="Image Tag")
parser.add_argument("dockerfile", parser.add_argument("dockerfile",
@ -164,10 +229,80 @@ class BuildCommand(SubCommand):
if dkr.image_matches_dockerfile(tag, dockerfile): if dkr.image_matches_dockerfile(tag, dockerfile):
if not args.quiet: if not args.quiet:
print "Image is up to date." print "Image is up to date."
return 0 else:
# Create a docker context directory for the build
docker_dir = tempfile.mkdtemp(prefix="docker_build")
# Is there a .pre file to run in the build context?
docker_pre = os.path.splitext(args.dockerfile)[0]+".pre"
if os.path.exists(docker_pre):
rc = subprocess.call(os.path.realpath(docker_pre),
cwd=docker_dir)
if rc == 3:
print "Skip"
return 0
elif rc != 0:
print "%s exited with code %d" % (docker_pre, rc)
return 1
# Do we include a extra binary?
if args.include_executable:
_copy_binary_with_libs(args.include_executable,
docker_dir)
dkr.build_image(tag, docker_dir, dockerfile,
quiet=args.quiet, argv=argv)
rmtree(docker_dir)
return 0
class UpdateCommand(SubCommand):
""" Update a docker image with new executables. Arguments: <tag> <executable>"""
name = "update"
def args(self, parser):
parser.add_argument("tag",
help="Image Tag")
parser.add_argument("executable",
help="Executable to copy")
def run(self, args, argv):
# Create a temporary tarball with our whole build context and
# dockerfile for the update
tmp = tempfile.NamedTemporaryFile(suffix="dckr.tar.gz")
tmp_tar = TarFile(fileobj=tmp, mode='w')
# Add the executable to the tarball
bn = os.path.basename(args.executable)
ff = "/usr/bin/%s" % bn
tmp_tar.add(args.executable, arcname=ff)
# Add any associated libraries
libs = _get_so_libs(args.executable)
if libs:
for l in libs:
tmp_tar.add(os.path.realpath(l), arcname=l)
# Create a Docker buildfile
df = StringIO()
df.write("FROM %s\n" % args.tag)
df.write("ADD . /\n")
df.seek(0)
df_tar = TarInfo(name="Dockerfile")
df_tar.size = len(df.buf)
tmp_tar.addfile(df_tar, fileobj=df)
tmp_tar.close()
# reset the file pointers
tmp.flush()
tmp.seek(0)
# Run the build with our tarball context
dkr = Docker()
dkr.update_image(args.tag, tmp, quiet=args.quiet)
dkr.build_image(tag, dockerfile, args.dockerfile,
quiet=args.quiet, argv=argv)
return 0 return 0
class CleanCommand(SubCommand): class CleanCommand(SubCommand):
@ -177,6 +312,12 @@ class CleanCommand(SubCommand):
Docker().clean() Docker().clean()
return 0 return 0
class ImagesCommand(SubCommand):
"""Run "docker images" command"""
name = "images"
def run(self, args, argv):
return Docker().command("images", argv, args.quiet)
def main(): def main():
parser = argparse.ArgumentParser(description="A Docker helper", parser = argparse.ArgumentParser(description="A Docker helper",
usage="%s <subcommand> ..." % os.path.basename(sys.argv[0])) usage="%s <subcommand> ..." % os.path.basename(sys.argv[0]))

View File

@ -0,0 +1,21 @@
# Create Debian Bootstrap Image
#
# This is intended to be pre-poluated by:
# - a first stage debootstrap (see debian-bootstrap.pre)
# - a native qemu-$arch that binfmt_misc will run
FROM scratch
# Add everything from the context into the container
ADD . /
# Patch all mounts as docker already has stuff set up
RUN sed -i 's/in_target mount/echo not for docker in_target mount/g' /debootstrap/functions
# Run stage 2
RUN /debootstrap/debootstrap --second-stage
# At this point we can install additional packages if we want
# Duplicate deb line as deb-src
RUN cat /etc/apt/sources.list | sed "s/deb/deb-src/" >> /etc/apt/sources.list
RUN apt-get update
RUN apt-get -y build-dep qemu

View File

@ -0,0 +1,87 @@
#!/bin/sh
#
# Simple wrapper for debootstrap, run in the docker build context
#
FAKEROOT=`which fakeroot 2> /dev/null`
exit_and_skip()
{
exit 3
}
#
# fakeroot is needed to run the bootstrap stage
#
if [ -z $FAKEROOT ]; then
echo "Please install fakeroot to enable bootstraping"
exit_and_skip
fi
# We check in order for
#
# - DEBOOTSTRAP_DIR pointing at a development checkout
# - PATH for the debootstrap script (installed)
#
# If neither option works then we checkout debootstrap from its
# upstream SCM and run it from there.
#
if [ -z $DEBOOTSTRAP_DIR ]; then
DEBOOTSTRAP=`which debootstrap 2> /dev/null`
if [ -z $DEBOOTSTRAP ]; then
echo "No debootstrap installed, attempting to install from SCM"
DEBOOTSTRAP_SOURCE=https://anonscm.debian.org/git/d-i/debootstrap.git
git clone ${DEBOOTSTRAP_SOURCE} ./debootstrap.git
export DEBOOTSTRAP_DIR=./debootstrap.git
DEBOOTSTRAP=./debootstrap.git/debootstrap
fi
else
DEBOOTSTRAP=${DEBOOTSTRAP_DIR}/debootstrap
if [ ! -f $DEBOOTSTRAP ]; then
echo "Couldn't find script at ${DEBOOTSTRAP}"
exit_and_skip
fi
fi
#
# Finally check to see if any qemu's are installed
#
BINFMT_DIR=/proc/sys/fs/binfmt_misc
if [ ! -e $BINFMT_DIR ]; then
echo "binfmt_misc needs enabling for a QEMU bootstrap to work"
exit_and_skip
else
# DEB_ARCH and QEMU arch names are not totally aligned
case "${DEB_ARCH}" in
amd64)
QEMU=qemu-i386
;;
armel|armhf)
QEMU=qemu-arm
;;
arm64)
QEMU=qemu-aarch64
;;
powerpc)
QEMU=qemu-ppc
;;
ppc64el)
QEMU=qemu-ppc64le
;;
s390)
QEMU=qemu-s390x
;;
*)
QEMU=qemu-${DEB_ARCH}
;;
esac
if [ ! -e "${BINFMT_DIR}/$QEMU" ]; then
echo "No binfmt_misc rule to run $QEMU, can't bootstrap"
exit_and_skip
fi
fi
echo "Building a rootfs using ${FAKEROOT} and ${DEBOOTSTRAP} ${DEB_ARCH}/${DEB_TYPE}"
${FAKEROOT} ${DEBOOTSTRAP} --variant=buildd --foreign --arch=$DEB_ARCH $DEB_TYPE . http://httpredir.debian.org/debian || exit 1
exit 0

View File

@ -11,6 +11,14 @@
# or (at your option) any later version. See the COPYING file in # or (at your option) any later version. See the COPYING file in
# the top-level directory. # the top-level directory.
set -e
if test -n "$V"; then
set -x
fi
BASE="$(dirname $(readlink -e $0))"
# Prepare the environment # Prepare the environment
. /etc/profile || true . /etc/profile || true
export PATH=/usr/lib/ccache:$PATH export PATH=/usr/lib/ccache:$PATH
@ -24,10 +32,10 @@ export TEST_DIR=/tmp/qemu-test
mkdir -p $TEST_DIR/{src,build,install} mkdir -p $TEST_DIR/{src,build,install}
# Extract the source tarballs # Extract the source tarballs
tar -C $TEST_DIR/src -xzf qemu.tgz tar -C $TEST_DIR/src -xzf $BASE/qemu.tgz
for p in dtc pixman; do for p in dtc pixman; do
if test -f $p.tgz; then if test -f $BASE/$p.tgz; then
tar -C $TEST_DIR/src/$p -xzf $p.tgz tar -C $TEST_DIR/src/$p -xzf $BASE/$p.tgz
export FEATURES="$FEATURES $p" export FEATURES="$FEATURES $p"
fi fi
done done
@ -55,4 +63,6 @@ elif test -n "$DEBUG"; then
echo echo
# Force error after shell exits # Force error after shell exits
$SHELL && exit 1 $SHELL && exit 1
else
exit 1
fi fi