Bug 1432390 - Make `mach taskcluster-build-image` talk directly to the docker socket in the image builder. r?dustin draft
authorMike Hommey <mh+mozilla@glandium.org>
Thu, 25 Jan 2018 13:36:47 +0900
changeset 747407 2ffde75bc224df7e9679758052bb2e5891a213ad
parent 747406 df22ccb689f6d3857f8f7d18cc38ef3f82a8749c
child 747408 71d24ffdbc7d7114e4b08f1631a451045c27ac66
push id96904
push userbmo:mh+mozilla@glandium.org
push dateFri, 26 Jan 2018 01:20:26 +0000
reviewersdustin
bugs1432390
milestone60.0a1
Bug 1432390 - Make `mach taskcluster-build-image` talk directly to the docker socket in the image builder. r?dustin Now that `mach taskcluster-build-image` can, we can avoid all the manual handling based on curl and jq in the image builder. An additional advantage on relying on `mach taskcluster-build-image` doing more is that less changes to the build-image.sh script will be necessary, and thus less updates of the image builder docker image.
taskcluster/docker/image_builder/build-image.sh
taskcluster/docker/image_builder/setup.sh
taskcluster/mach_commands.py
taskcluster/taskgraph/docker.py
--- a/taskcluster/docker/image_builder/build-image.sh
+++ b/taskcluster/docker/image_builder/build-image.sh
@@ -13,55 +13,35 @@ raise_error() {
   exit 1
 }
 
 # Ensure that the PROJECT is specified so the image can be indexed
 test -n "$PROJECT"    || raise_error "PROJECT must be provided."
 test -n "$HASH"       || raise_error "Context HASH must be provided."
 test -n "$IMAGE_NAME" || raise_error "IMAGE_NAME must be provided."
 
-# Construct a CONTEXT_FILE
-CONTEXT_FILE=/builds/worker/workspace/context.tar
+# The docker socket is mounted by the taskcluster worker in a way that prevents
+# us changing its permissions to allow the worker user to access it. Create a
+# proxy socket that the worker user can use.
+export DOCKER_SOCKET=/var/run/docker.proxy
+socat UNIX-LISTEN:$DOCKER_SOCKET,fork,group=worker,mode=0775 UNIX-CLIENT:/var/run/docker.sock </dev/null &
+trap "kill $!" EXIT
 
-# Run ./mach taskcluster-build-image with --context-only to build context
+# Build image
 run-task \
   --vcs-checkout "/builds/worker/checkouts/gecko" \
   --sparse-profile build/sparse-profiles/docker-image \
   -- \
   /builds/worker/checkouts/gecko/mach taskcluster-build-image \
-  --context-only "$CONTEXT_FILE" \
+  -t "$IMAGE_NAME:$HASH" \
   "$IMAGE_NAME"
-test -f "$CONTEXT_FILE" || raise_error "Context file wasn't created"
 
 # Create artifact folder (note that this must occur after run-task)
 mkdir -p /builds/worker/workspace/artifacts
 
-# Post context tar-ball to docker daemon
-# This interacts directly with the docker remote API, see:
-# https://docs.docker.com/engine/reference/api/docker_remote_api_v1.18/
-curl -s --fail \
-  -X POST \
-  --header 'Content-Type: application/tar' \
-  --data-binary "@$CONTEXT_FILE" \
-  --unix-socket /var/run/docker.sock "http:/build?t=$IMAGE_NAME:$HASH" \
-  | tee /tmp/docker-build.log \
-  | jq -jr '(.status + .progress, .error | select(. != null) + "\n"), .stream | select(. != null)'
-
-# Exit non-zero if there is error entries in the log
-if result=$(jq -se 'add | .error' /tmp/docker-build.log); then
-  raise_error "Image build failed: ${result}";
-fi
-
-# Sanity check that image was built successfully
-if ! tail -n 1 /tmp/docker-build.log | jq -r '.stream' | grep '^Successfully built' > /dev/null; then
-  echo 'docker-build.log for debugging:';
-  tail -n 50 /tmp/docker-build.log;
-  raise_error "Image build log didn't with 'Successfully built'";
-fi
-
 # Get image from docker daemon (try up to 10 times)
 # This interacts directly with the docker remote API, see:
 # https://docs.docker.com/engine/reference/api/docker_remote_api_v1.18/
 #
 # The script will retry up to 10 times.
 # Disable quoting error until fixing the / escaping
 # shellcheck disable=SC2086
 /usr/local/bin/download-and-compress \
--- a/taskcluster/docker/image_builder/setup.sh
+++ b/taskcluster/docker/image_builder/setup.sh
@@ -3,19 +3,17 @@ set -v -e -x
 
 export DEBIAN_FRONTEND=noninteractive
 
 # Update apt-get lists
 apt-get update -y
 
 # Install dependencies
 apt-get install -y --no-install-recommends \
-    curl \
-    tar \
-    jq \
+    socat \
     python \
     python-requests \
     python-requests-unixsocket
 
 # Extra dependencies only needed for image building. Will be removed at
 # end of script.
 apt-get install -y python-pip
 
--- a/taskcluster/mach_commands.py
+++ b/taskcluster/mach_commands.py
@@ -416,25 +416,28 @@ class TaskClusterImagesProvider(MachComm
         except Exception:
             traceback.print_exc()
             sys.exit(1)
 
     @Command('taskcluster-build-image', category='ci',
              description='Build a Docker image')
     @CommandArgument('image_name',
                      help='Name of the image to build')
+    @CommandArgument('-t', '--tag',
+                     help="tag that the image should be built as.",
+                     metavar="name:tag")
     @CommandArgument('--context-only',
                      help="File name the context tarball should be written to."
                           "with this option it will only build the context.tar.",
                      metavar='context.tar')
-    def build_image(self, image_name, context_only):
+    def build_image(self, image_name, tag, context_only):
         from taskgraph.docker import build_image, build_context
         try:
             if context_only is None:
-                build_image(image_name, os.environ)
+                build_image(image_name, tag, os.environ)
             else:
                 build_context(image_name, context_only, os.environ)
         except Exception:
             traceback.print_exc()
             sys.exit(1)
 
 
 @CommandProvider
--- a/taskcluster/taskgraph/docker.py
+++ b/taskcluster/taskgraph/docker.py
@@ -59,29 +59,29 @@ def build_context(name, outputFile, args
 
     image_dir = docker.image_path(name)
     if not os.path.isdir(image_dir):
         raise Exception('image directory does not exist: %s' % image_dir)
 
     docker.create_context_tar(GECKO, image_dir, outputFile, "", args)
 
 
-def build_image(name, args=None):
+def build_image(name, tag, args=None):
     """Build a Docker image of specified name.
 
     Output from image building process will be printed to stdout.
     """
     if not name:
         raise ValueError('must provide a Docker image name')
 
     image_dir = docker.image_path(name)
     if not os.path.isdir(image_dir):
         raise Exception('image directory does not exist: %s' % image_dir)
 
-    tag = docker.docker_image(name, by_tag=True)
+    tag = tag or docker.docker_image(name, by_tag=True)
 
     buf = BytesIO()
     docker.stream_context_tar(GECKO, image_dir, buf, '', args)
     docker.post_to_docker(buf.getvalue(), '/build', nocache=1, t=tag)
 
     print('Successfully built %s and tagged with %s' % (name, tag))
 
     if tag.endswith(':latest'):