#!/usr/bin/env bash
# walt-image-build-helper is a build script run when the server
# has to handle a "walt image build" command requested by a client.
set -e

export LC_ALL=C

mode=""
user=""
image_fullname=""
debug=0
subdir=""

while [ ! -z "$1" ]
do
    case "$1" in
        "--from-url")
            shift
            url="$1"
            if [ ! -z "$url" ]
            then
                mode=url
                shift
            fi
            ;;
        "--sub-dir")
            shift
            subdir="$1"
            shift
            ;;
        "--from-stdin")
            mode=stdin
            shift
            ;;
        "--from-node-diff")
            shift
            node_diff_dump_cmd="$1"
            from_image_fullname="$2"
            shift 2
            mode="node-diff"
            ;;
        "--debug")
            debug=1
            shift
            ;;
        *)
            user="$1"
            image_fullname="$2"
            shift 2
            break
    esac
done

if [ -z "$mode" -o -z "$user" -o -z "$image_fullname" -o ! -z "$1" ]
then
	echo "Usage:" >&2
    echo "  $0 [--debug] --from-url <git-remote-build-repo>" \
         "[--sub-dir <path>] <user> <image_fullname>" >&2
    echo "  tar cf - . | $0 [--debug] --from-stdin" \
         "<user> <image_fullname>" >&2
    echo "  $0 [--debug] --from-node-diff <node-diff-dump-cmd>" \
         "<node-image-fullname> <user> <image_fullname>" >&2
	exit
fi

if [ $debug -eq 1 ]
then
    set -x
fi

tmp_dir=$(mktemp -d)

on_exit() {
    # save return code
    retcode="$?"
    # cleanup
    cd /tmp
    rm -rf "${tmp_dir}"
    # print failure message if relevant
    if [ "$retcode" -ne 0 ]
    then
        echo "Sorry, image build FAILED." >&2
    fi
}

trap "on_exit" EXIT

verify_repo() {
    echo "** Verifying the repository"
    precision=""
    if [ ! -f Dockerfile -a ! -f ContainerFile ]
    then
        if [ "$mode" = "url" ]
        then
            word="repository"
            if [ ! -z "$subdir" ]
            then
                precision=" in sub-directory '$subdir'"
            fi
        else
            word="directory"
        fi
        echo "FAILED: the specified $word does not contain a Dockerfile" \
             "or ContainerFile${precision}."
        exit 1
    fi
}

clone_from_url() {
    # note: 'buildah bud' should be able to use a remote git URL, but it seems too picky,
    # so use git clone ourselves.
    echo "** Cloning the git repository at $url"
    git clone --depth 1 --single-branch "$url" .
}

receive_tar() {
    echo "** Receiving the client directory content"
    tar x
}

prepare_build_dir_from_node_diff() {
    echo "** Analysing changes made on this node"
    $node_diff_dump_cmd > ar.tar
    cat > Dockerfile << EOF
FROM ${from_image_fullname}
ADD ar.tar /
EOF
}

# We will modify FROM directives of the Dockerfile which do not specify
# the "<user>/" part.
#
# These directive are implicit and may match several images,
# for instance "rpi-default" may refer to one of two or more different
# images present on the server, e.g., "waltplatform/rpi-default:latest"
# and "eduble/rpi-default:latest".
#
# In this case podman build would silently use one of them, the first
# it finds.
#
# If "eduble" is the user who started "walt image build" with this
# Dockerfile, he probably meant "eduble/rpi-default:latest", so
# let's alter the relevant lines in this way.

_replace_implicit_from_lines() {
    stages=" "
    # grep: remove comments
    # sed: handle "\" line continuation char
    grep -v "^#" Dockerfile | \
    sed -e :a -e '/\\$/N; s/\\\n//; ta' | \
    while read cmd args
    do
        cmd="${cmd^^}"    # uppercase
        if [ "$cmd" = "FROM" ]
        then
            echo -n "FROM"
            state="read_image"
            for arg in $args
            do
                case $state in
                    "read_image")
                        case "$arg" in
                            "--"*)
                                # this is an option, continue with next arg
                                echo -n " ${arg}"
                                continue
                                ;;
                            "scratch")
                                # special "scratch" stage
                                echo -n " ${arg}"
                                ;;
                            *"/"*)
                                # user part already specified
                                echo -n " ${arg}"
                                ;;
                            *)
                                case "$stages" in
                                    *" ${arg} "*)
                                        # it is the name of a previous stage
                                        echo -n " ${arg}"
                                        ;;
                                    *)
                                        # user part apparently missing
                                        echo -n " ${user}/${arg}"
                                        ;;
                                esac
                                ;;
                        esac
                        state="read_as"
                        ;;
                    "read_as")
                        if [ "${arg^^}" = "AS" ]
                        then
                            echo -n " ${arg}"
                            state="read_stage"
                        else
                            echo "Warning: failed to analyse FROM line." >&2
                            return 1
                        fi
                        ;;
                    "read_stage")
                        stages="${stages}${arg} "
                        echo -n " ${arg}"
                        ;;
                esac
            done
            echo  # end of line
        else
            echo "$cmd $args"
        fi
    done > Dockerfile.__fixed__
    return 0  # OK
}

replace_implicit_from_lines() {
    if _replace_implicit_from_lines
    then
        mv Dockerfile.__fixed__ Dockerfile
    else
        # failed, leave Dockerfile unchanged
        rm Dockerfile.__fixed__
    fi
}

podman_build() {
    # If it was named ContainerFile, rename it to Dockerfile
    # to avoid further tests in next commands
    if [ ! -f Dockerfile ]
    then
        mv ContainerFile Dockerfile
    fi
    # Ensure FROM lines specify a user
    replace_implicit_from_lines
    # Build
    podman build --format docker -f Dockerfile \
            -t "docker.io/$image_fullname" .
}

build_image() {
    echo "** Building the image"
    if [ "$1" = "--silent" ]
    then
        podman_build >/dev/null
    else
        podman_build
    fi
}

verify_image() {
    echo "** Verifying the image"
    walt-image-check "docker.io/$image_fullname"
}

cd "$tmp_dir"
if [ "$mode" = "url" ]
then
    clone_from_url
    if [ ! -z "$subdir" ]
    then
        if [ ! -d "$subdir" ]
        then
            echo "No such directory \"$subdir\"." >&2
            exit 1
        fi
        cd "$subdir"
    fi
    verify_repo
    build_image
    verify_image
elif [ "$mode" = "node-diff" ]
then
    prepare_build_dir_from_node_diff
    build_image --silent
    verify_image
elif [ "$mode" = "stdin" ]
then
    receive_tar
    verify_repo
    build_image
    verify_image
fi
