#!/usr/bin/env bash # Depends on bash 4 and gawk TOPDIR=$(git rev-parse --show-toplevel) # Work in a temp dir so that developers can continue working while a build is # in progress WORKDIR=$(mktemp -d /tmp/kolla-workdir.XXXXXXXXXX) # Remove $WORKDIR otherwise $TOPDIR is copied *inside* of it rm -rf $WORKDIR cp -aL "$TOPDIR" $WORKDIR DOCKERDIR="$WORKDIR/docker" declare -A dependency declare -A img_dirs declare -A status function info { [[ -n $1 ]] && printf "%s\n" "$1" } function success { [[ -n $1 ]] && printf "\033[00;32m%s\033[00m\n" "$1" } function warn { [[ -n $1 ]] && printf "\033[00;31m%s\033[00m\n" "$1" } function set_defaults { PREFIX=centos-rdo- NAMESPACE=kollaglue } function has_changed { local image=$1 # Rebuild everything unless given git revision # We don't really care about the order of the $FROM and $TO parameters, all # we need is the list of changed files between the revisions, or HEAD if # only one of them is specified [[ -z $FROM && -z $TO ]] || git diff --name-only $FROM $TO | grep -q "${img_dirs[$image]#$WORKDIR/}" } function requires_build { local image=$1 local dep=${dependency[$image]} # An image requires a built if it meets the following conditions: # - it has instructions to build the image # - it hasn't been processed yet # - its dependency was rebuilt or its build instruction was modified [[ ${img_dirs[$image]} && -z ${status[$image]} ]] && \ ([[ ${status[$dep]} == "rebuilt" ]] || has_changed $image) } function build_image { local dir=$1 if [ -x "$dir/build" ]; then printf "\n" info "Building image in $dir" if $dir/build $ARGS --no-use-released-parent; then success "Successfully built image in $dir" status[$image]="rebuilt" else warn "Failed to build image in $dir" status[$image]="fail" fi fi } function init_image { local img_dir=$1 set_defaults [ -f $WORKDIR/.buildconf ] && . $WORKDIR/.buildconf [ -f $img_dir/.buildconf ] && . $img_dir/.buildconf [ -n "$FORCE_NAMESPACE" ] && NAMESPACE=$FORCE_NAMESPACE local image="${NAMESPACE:+${NAMESPACE}/}${PREFIX}${img_dir##*/}" local base_image=$(cat $img_dir/Dockerfile | gawk 'match($0, /^\s*FROM\s+(\S+)/, matches) {print matches[1]}' ) base_image=${base_image//%%KOLLA_NAMESPACE%%/$NAMESPACE} base_image=${base_image//%%KOLLA_PREFIX%%/$PREFIX} base_image=${base_image//:%%KOLLA_TAG%%/} img_dirs[$image]=$img_dir dependency[$image]=$base_image # Restore defaults to minimize risk of side effects set_defaults } function process_image { local image=$1 if [ -n "${dependency[$image]}" ]; then process_image ${dependency[$image]} fi if requires_build $image; then build_image ${img_dirs[$image]} fi if [ -z "${status[$image]}" ]; then status[$image]="up-to-date" fi } function print_summary { printf "\nSummary\n=======\n" for image in "${!status[@]}"; do case "${status[$image]}" in ("fail") warn "Failed to process $image" ;; ("rebuilt") success "Rebuilt $image" ;; (*) info "$image: ${status[$image]}" ;; esac done } function interrupted { info "Interrupted..." print_summary rm -rf $WORKDIR exit 1 } function usage () { read -r -d '' HELP < --to $($WORKDIR/tools/build-docker-image --help) EOF printf "%s\n" "${HELP/$WORKDIR\/tools\/build-docker-image/$0}" } trap 'interrupted' INT ARGS=$@ PARSED_ARGS=$(getopt -q -o hn: -l help,namespace:,from:,to: -- "$@") eval set -- "$PARSED_ARGS" while :; do case "$1" in (--help|-h) usage exit 0 ;; (--namespace|-n) shift FORCE_NAMESPACE="$1" ;; (--from) shift FROM="$1" ARGS=${ARGS/\-\-from*$FROM/} ;; (--to) shift TO="$1" ARGS=${ARGS/\-\-to*$TO/} ;; (--) break ;; esac shift done # Do a first pass to find images to build and their dependencies for dockerfile in $(find $DOCKERDIR -name Dockerfile); do init_image $(dirname $dockerfile) done # Process all images for image in "${!img_dirs[@]}"; do process_image $image done print_summary rm -rf $WORKDIR