#!/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 [ -f $WORKDIR/.buildconf ] && . $WORKDIR/.buildconf [ -n "${FORCE_PREFIX}" ] && PREFIX="${FORCE_PREFIX}" } 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 printf "\n" if [ -x "$dir/build" ]; then 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 else warn "Image $image does not provide build script" status[$image]="fail" 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 [ -n "${REGISTRY}" ] && NAMESPACE="${REGISTRY}/${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 print_parseable_summary { printf "{" for image in "${!status[@]}"; do printf "'$image':'${status[$image]}'," done printf "}" } function interrupted { info "Interrupted..." print_summary rm -rf $WORKDIR exit 1 } function usage { read -r -d '' HELP < --from --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 hr:n:t: -l help,prefix:,namespace:,release,tag:,private-registry:,from:,to:,testmode -- "$@") eval set -- "$PARSED_ARGS" while :; do case "$1" in (--help|-h) usage exit 0 ;; (--prefix) shift FORCE_PREFIX="$1" ;; (--namespace|-n) shift FORCE_NAMESPACE="$1" ;; (--release) RELEASE_SPECIFIED=1 ;; (--tag|-t) shift TAG_SPECIFIED=1 ;; (--private-registry|-r) shift REGISTRY="$1" ;; (--from) shift FROM="$1" ARGS=${ARGS/\-\-from*$FROM/} ;; (--to) shift TO="$1" ARGS=${ARGS/\-\-to*$TO/} ;; (--testmode) TESTMODE=1 ARGS=${ARGS/\-\-testmode/} ;; (--) break ;; esac shift done set_defaults # BASE == centos, fedora, ubuntu, etc. BASE=$(echo "${PREFIX}" | cut -d- -f1) # TYPE == binary, source, rdo TYPE=$(echo "${PREFIX}" | cut -d- -f2) # Ensure a tag is specified otherwise build may fail when changing git branch # The release tag is automatically appended when --release flag is used. if [[ $RELEASE_SPECIFIED != 1 ]] && [[ $TAG_SPECIFIED != 1 ]]; then TAG=$(git rev-parse --short HEAD) ARGS+=" --tag $TAG" fi # Do a first pass to find images to build and their dependencies for dockerfile in $(find "${DOCKERDIR}/${BASE}/${TYPE}" -name Dockerfile); do init_image $(dirname $dockerfile) done # Process all images for image in "${!img_dirs[@]}"; do process_image $image done print_summary [ -n "$TESTMODE" ] && print_parseable_summary rm -rf $WORKDIR