#!/bin/bash ## Updates an existing image with given script coming from stdin. ## exname=$(basename $0) usage="$exname DOCKER_IMAGE Update image with stdin instruction. " if [ -z "$1" ]; then echo "Error: you should specify a parent image as first argument." >&2 echo "usage: $usage" exit 1 fi image="$1" shift DOCKER_VERSION=$(docker -v | sed -r 's/^[^0-9.]+([0-9.]+).*$/\1/g') is_cached_code_same() { local cmd cmd="$@" code="$(cat -)" [ "$1" == "-c" ] || return 1 [ "$2" == "$code" ] || return 1 if echo "$code" | egrep "#\s*docker:\s*ALWAYS\s*" >/dev/null 2>&1; then return 1 fi return 0 } DOCKER_INSPECT_CACHE_FORMAT='{{.Id}}{{"\x00"}} {{.Parent}}{{"\x00"}} {{if .Config}}{{range $p := .Config.Cmd}}{{$p}}{{"\x00"}} {{end}}{{end}}{{"\x00"}}' DOCKER_INSPECT_CACHE_FORMAT="$(echo "$DOCKER_INSPECT_CACHE_FORMAT" | tr -d "\n")" export DOCKER_INSPECT_CACHE_FORMAT ## # check_cache PARENT_IMAGE # # Checks if no already stored image has PARENT_IMAGE as parent and # same code. If it founds such an image, it'll output its id in stdout # and return 0 errlvl. Otherwise it'll return 1 errlvl. check_cache() { local image image="$1" shift code="$(cat -)" image_id=$(get_image_id "$image") while read-0 id parent; do cmd=() while true; do read-0 elt if [ "$elt" ]; then cmd=("${cmd[@]}" "$elt") else break fi done if [ "$parent" != "$image_id" ]; then continue fi if echo "$code" | is_cached_code_same "${cmd[@]}"; then ## not used "$id" as there is a spurious \n that is inserted by --format echo $id return 0 fi done < <(docker inspect --format "$DOCKER_INSPECT_CACHE_FORMAT" $(docker images -qa)) return 1 } docker_update() { local image image="$1" shift code=$(cat -) ## Saving CMD and ENTRYPOINT save_cmd=$(docker inspect --format='{{json .Config.Cmd}}' "$image") save_entrypoint=$(docker inspect --format='{{json .Config.Entrypoint}}' "$image") ## Checking cache in case already built. if cached=$(echo "$code" | check_cache "$image"); then echo "cached" docker tag "$cached" "$image" else ## Running code in container container_id=$(docker run -d \ --entrypoint /bin/bash \ "$@" "$image" -c \ "$code" ) if [ "$(docker wait "$container_id")" != "0" ]; then echo "Update of image "$image" failed ! (container: $container_id)" echo "=== code:" echo "$code" echo "=== logs:" docker logs "$container_id" exit 1 fi docker commit --author "$exname" --message "$exname" \ "$container_id" "$image" >/dev/null && docker rm $container_id >/dev/null echo "updated" fi ## Final commit to restore CMD and ENTRYPOINT docker build -t "$image" - </dev/null 2>&1 FROM $image CMD $save_cmd ENTRYPOINT $save_entrypoint EOF } read-0() { while [ "$1" ]; do IFS=$'\0' read -r -d '' "$1" || return 1 shift done } get_image_id() { docker inspect --format '{{.Id}}' "$1" } docker_update "$image" "$@"