Add scripts to simplify creation of multiarch images (#157)

diff --git a/README.md b/README.md
index 7f7d73a..cf953d8 100644
--- a/README.md
+++ b/README.md
@@ -239,6 +239,13 @@
 docker run -it -p 15984:15984 -p 25984:25984 <image-hash> -n 2
 ```
 
+# Image building for CouchDB release managers
+
+Check out the `build.sh` script in the apache/couchdb-docker GitHub repository,
+which can build images for any version, even in a cross-platform way.
+
+Also, read the next section to ensure you push all of the tags necessary.
+
 # Image uploading for CouchDB release managers
 
 Taking a hypothetical example of CouchDB 2.9.7, here's all of the tags you'd want:
@@ -255,7 +262,11 @@
 docker push apache/couchdb:latest
 ```
 
-Obviously don't create/push the `latest` or `2` tags if this is a maintenance branch superceded by a newer one.
+Obviously don't create/push the `latest` or `2` tags if this is a maintenance
+branch superceded by a newer one.
+
+The `build.sh` utility can help you do this quickly, see its usage help for
+more details.
 
 ## Feedback, Issues, Contributing
 
diff --git a/build.sh b/build.sh
new file mode 100755
index 0000000..70bea97
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,322 @@
+#!/usr/bin/env bash
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing,
+#   software distributed under the License is distributed on an
+#   "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#   KIND, either express or implied.  See the License for the
+#   specific language governing permissions and limitations
+#   under the License.
+
+# This shell script makes it easier to build multi-platform
+# architecture Docker containers on an x86_64 host.
+#
+# For more reading:
+#   https://github.com/multiarch/qemu-user-static
+#   https://lobradov.github.io/Building-docker-multiarch-images/
+#   https://github.com/jessfraz/irssi/blob/master/.travis.yml
+#   https://engineering.docker.com/2019/04/multi-arch-images/
+#   https://github.com/docker/buildx
+
+set -e
+
+PROMPT="Are you sure (y/n)? "
+QEMU="YES"
+PLATFORMS="amd64 arm64v8 ppc64le"
+BUILDX_PLATFORMS="linux/amd64,linux/arm64/v8,linux/ppc64le"
+
+prompt() {
+  if [ -z "${PROMPT}" ]
+  then
+    return
+  fi
+  if [ "$1" ]
+  then
+    echo "$1"
+  fi
+  read -p "${PROMPT}"
+  if [[ $REPLY =~ ^[Yy]$ ]]
+  then
+    return
+  else
+    exit 0
+  fi
+}
+
+update_qemu() {
+  # necessary locally after every reboot, not sure why....update related maybe?
+  # basically harmless to run everytime, except for elevated privs necessary.
+  # disable with -n flag
+  docker rmi multiarch/qemu-user-static >/dev/null 2>&1 || true
+  docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
+  docker rmi multiarch/qemu-user-static
+}
+
+clean() {
+  echo $#
+  if [ $# -eq 0 ]
+  then
+    regex="*"
+    ADD_PROMPT="This will remove *ALL* local apache/couchdb Docker images!"
+  elif [ $# -eq 1 ]
+  then
+    regex=$1
+    ADD_PROMPT="This will remove *ALL* apache/couchdb images matching regex '${1}' !"
+  else
+    usage
+  fi
+  prompt "${ADD_PROMPT}"
+
+  docker images --filter=reference="apache/couchdb:${regex}" | tr -s ' ' | cut -d ' ' -f 2 | while read tag
+  do
+    if [ ${tag} ] && [ ${tag} = "TAG" ]
+    then
+      continue
+    fi
+    docker rmi apache/couchdb:$tag
+  done
+}
+
+# Builds a specific version
+build() {
+  VERSION=$1
+  ARCH=${2:-amd64}
+  FROMIMG="$(awk '$1 == toupper("FROM") { print $2 }' $VERSION/Dockerfile)"
+  CURRARCH=$(docker run --rm -t ${FROMIMG} uname -m)
+
+  if [ ${CURRARCH} != ${ARCH} ]
+  then
+    docker rmi ${FROMIMG}
+    docker pull "${ARCH}/${FROMIMG}"
+    docker tag "${ARCH}/${FROMIMG}" "${FROMIMG}"
+  fi
+  docker build -t apache/couchdb:${ARCH}-${VERSION} ${VERSION}
+  echo "CouchDB ${VERSION} for ${ARCH} built as apache/couchdb:${ARCH}-${VERSION}."
+}
+
+# Builds all platforms for a specific version, local only
+# We can't do this with docker buildx, see https://github.com/docker/buildx/issues/166#issuecomment-562729523
+build-all() {
+  VERSION=$1
+  for ARCH in ${PLATFORMS}; do
+    echo "Starting ${ARCH} at $(date)..."
+    build $1 ${ARCH}
+    echo ""
+  done
+}
+
+# Push locally built versions using above technique
+push() {
+  if [ $2 ]
+  then
+    tag_as=$2
+  else
+    tag_as=$1
+  fi
+  docker manifest create apache/couchdb:$tag_as \
+    apache/couchdb:amd64-$1 \
+    apache/couchdb:arm64v8-$1 \
+    apache/couchdb:ppc64le-$1
+
+  docker manifest annotate apache/couchdb:$tag_as \
+    apache/couchdb:arm64v8-$1 --os linux --arch arm64 --variant v8
+
+  docker manifest annotate apache/couchdb:$tag_as \
+    apache/couchdb:ppc64le-$1 --os linux --arch ppc64le
+
+  docker manifest push --purge apache/couchdb:$tag_as
+
+  docker manifest inspect apache/couchdb:$tag_as
+}
+
+# Builds all platforms for a specific version and pushes to the registry
+buildx() {
+  if [ $2 ]
+  then
+    tag_as=$2
+  else
+    tag_as=$1
+  fi
+  docker buildx rm apache-couchdb >/dev/null 2>&1 || true
+  docker buildx create --name apache-couchdb
+  docker buildx use apache-couchdb
+  docker buildx inspect --bootstrap
+
+  echo "Starting buildx build at $(date)..."
+  docker buildx build --platform ${BUILDX_PLATFORMS} --tag apache/couchdb:$tag_as --push $1
+  echo ""
+}
+
+usage() {
+  cat << EOF
+$0 <command> <-f> <-n> [OPTIONS]
+
+Options:
+  -f                     Skip confirmation prompt.
+  -n                     Do not install QEMU and binfmt_misc
+                         (build commands only)
+
+General commands:
+  clean                  Removes ALL local apache/couchdb images (!!)
+  clean <regex>          Removes ALL local images with matching tags.
+
+\`docker build\` commands:
+  version #.#.# [all]    Builds all platforms for supplied version
+                         Each platform is tagged <arch>-<version>.
+
+  version #.#.# <arch>   Builds only the specified version and arch.
+
+  push #.#.# [as <tag>]  Pushes locally-built versions as a multi-arch
+                         manifest. If \`as <tag>\` is specified,
+                         pushes the manifest using that tag instead.
+
+Example workflow:
+  $0 clean *2.9.7*
+  $0 version 2.9.7 all
+  <test, then>
+  $0 push 2.9.7
+  $0 push 2.9.7 as 2.9
+  $0 push 2.9.7 as 2
+  $0 push 2.9.7 as latest
+
+\`docker buildx\` commands:
+  buildx #.#.#           Builds *and pushes* all platforms for supplied
+                         version, using docker buildx. Built images must
+                         be retrieved with \`docker pull\` for local use.
+
+  buildx #.#.# as <tag>
+                         Builds and pushes all platforms for supplied
+                         version, using docker buildx, tagging the
+                         manifest with the supplied <tag>.
+
+Example workflow:
+  $0 clean *2.9.7*
+  $0 buildx 2.9.7
+  $0 buildx 2.9.7 as 2.9
+  $0 buildx 2.9.7 as 2
+  $0 buildx 2.9.7 as latest
+  docker manifest inspect apache/couchdb:2.9.7
+  docker pull <--platform linux/other-arch> apache/couchdb:2.9.7 (for testing)
+
+
+NOTE: Requires Docker 19.03+ with experimental features enabled.
+      Add { "experimental" : "true" } to /etc/docker/daemon.json, then
+      add { "experimental": "enabled" } to ~/.docker/config.json, then
+      restart the Docker daemon.
+
+EOF
+exit 0
+}
+
+# #######################
+
+# handle -f/-n anywhere they appear on the CLI
+POSITIONAL=()
+while [[ $# -gt 0 ]]
+do
+  # otherwise, we WILL match a regex against top-level directories!
+  set -f
+  key="$1"
+  case $key in
+    -f|--force)
+      unset PROMPT
+      shift
+      ;;
+    -n|--no-qemu)
+      unset QEMU
+      shift
+      ;;
+    *)
+      POSITIONAL+=("$1")
+      shift
+      ;;
+  esac
+  set +f
+done
+# re-set all other arguments into argc
+set -- "${POSITIONAL[@]}" # restore positional parameters
+
+case "$1" in
+  clean)
+    # removes local images for a given version (and optionally platform)
+    shift
+    set -f
+    clean $*
+    set +f
+    ;;
+  version)
+    # builds a specific version using docker build
+    # validate/reinstall QEMU
+    if [ ${QEMU} ]
+    then
+      update_qemu
+    fi
+    shift
+    if [ $# -lt 1 -o $# -gt 3 ]
+    then
+      usage
+    fi
+    # version #.#.# all
+    if [ "$2" = "all" ]
+    then
+      # build all the platforms and test them locally
+      build-all $1
+    else
+      # build a specific platform locally
+      build $1 $2
+    fi
+    ;;
+  push)
+    # pushes already built local versions as manifest
+    shift
+    if [ $# -ne 1 -a $# -ne 3 ]
+    then
+      usage
+    fi
+    if [ $# -eq 1 ]
+    then
+      push $1
+    elif [ $2 = "as" ]
+    then
+      push $1 $3
+    else
+      usage
+    fi
+    ;;
+  buildx)
+    # builds and pushes using docker buildx
+    if [ ${QEMU} ]
+    then
+      update_qemu
+    fi
+    shift
+    if [ $# -ne 1 -a $# -ne 3 ]
+    then
+      usage
+    fi
+    if [ $# -eq 1 ]
+    then
+      buildx $1
+    elif [ $2 = "as" ]
+    then
+      buildx $1 $3
+    else
+      usage
+    fi
+    ;;
+  usage)
+    usage
+    ;;
+  *)
+    usage
+    ;;
+esac