#################################################################
#
# Docker specific functions.
#
################################################################
#
# Copyright (c) 2017 SUSE Linux Products GmbH
# Copyright (c) 2021 SUSE LLC
# Copyright (c) 2022 Andreas Stieger <Andreas.Stieger@gmx.de>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program (see the file COPYING); if not, write to the
# Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
#
################################################################

DOCKERD_STARTED=

recipe_setup_docker() {
    TOPDIR="/usr/src/packages"
    test "$DO_INIT_TOPDIR" != false && rm -Rf "$BUILD_ROOT/$TOPDIR"
    mkdir -p "$BUILD_ROOT$TOPDIR/SOURCES"
    if test "$MYSRCDIR" = $BUILD_ROOT/.build-srcdir ; then
        mv "$MYSRCDIR"/* $BUILD_ROOT$TOPDIR/SOURCES/
    else
        if test -z "$LINKSOURCES" ; then 
	    copy_sources "$MYSRCDIR" "$BUILD_ROOT$TOPDIR/SOURCES/"
        else
            cp -lR "$MYSRCDIR"/* $BUILD_ROOT$TOPDIR/SOURCES/ || cleanup_and_exit 1 "source copy failed"
        fi
    fi  
    chown -hR "$ABUILD_UID:$ABUILD_GID" "$BUILD_ROOT$TOPDIR"
    if test -z "$ABUILD_TARGET"; then
        ABUILD_TARGET=$(queryconfig target --dist "$BUILD_DIST" --configdir "$CONFIG_DIR" --archpath "$BUILD_ARCH" )
        test -z "$ABUILD_TARGET" || echo "build target is $ABUILD_TARGET"
    fi
}

recipe_prepare_docker() {
    :
}

dapper_read_env() {
    local tag="$1"
    DAPPER_SOURCE=
    DAPPER_CP=
    DAPPER_OUTPUT=
    DAPPER_DOCKER_SOCKET=
    DAPPER_RUN_ARGS=
    DAPPER_ENV=
    local e
    while read e ; do
	case "$e" in
	    DAPPER_SOURCE=*) DAPPER_SOURCE="${e#*=}" ;;
	    DAPPER_CP=*) DAPPER_CP="${e#*=}" ;;
	    DAPPER_OUTPUT=*) DAPPER_OUTPUT="${e#*=}" ;;
	    DAPPER_DOCKER_SOCKET=*) DAPPER_DOCKER_SOCKET="${e#*=}" ;;
	    DAPPER_RUN_ARGS=*) DAPPER_RUN_ARGS="${e#*=}" ;;
	    DAPPER_ENV=*) DAPPER_ENV="${e#*=}" ;;
	esac
    done < <($DOCKER_CMD inspect --format='{{range .Config.Env}}{{println .}}{{end}}' "$tag")
    test -z "$DAPPER_SOURCE" && DAPPER_SOURCE=/source/
    test -z "$DAPPER_CP" && DAPPER_CP=.
    test -z "$DAPPER_OUTPUT" && DAPPER_OUTPUT=.
    DAPPER_SOURCE="${DAPPER_SOURCE%/}/"
    DAPPER_MODE=cp
}

dapper_run() {
    local tag="$1"
    dapper_read_env "$tag"
    # copyin
    mkdir -p "$BUILD_ROOT$TOPDIR/BUILD"
    echo "FROM $tag" > "$BUILD_ROOT$TOPDIR/BUILD/Dockerfile.copyin"
    echo "COPY $DAPPER_CP $DAPPER_SOURCE" >> "$BUILD_ROOT$TOPDIR/BUILD/Dockerfile.copyin"
    $DOCKER_CMD build -f "$TOPDIR/BUILD/Dockerfile.copyin" -t "$tag" "$TOPDIR/SOURCES/" || cleanup_and_exit 1 "dapper source copyin failed"
    # run
    local args=(-i --name build-dapper-run)
    if test "$DAPPER_DOCKER_SOCKET" = true ; then
	args=("${args[@]}" -v "/var/run/docker.sock:/var/run/docker.sock")
    fi
    args=("${args[@]}" -e "DAPPER_UID=0")
    args=("${args[@]}" -e "DAPPER_GID=0")
    local e
    set -f
    for e in $DAPPER_ENV ; do
	args=("${args[@]}" -e "$e")
    done
    for e in $DAPPER_RUN_ARGS ; do
	args=("${args[@]}" "$e")
    done
    set +f
    if ! $DOCKER_CMD run --network=host "${args[@]}" "$tag" ; then
	cleanup_and_exit 1 "$DOCKER_TOOL run command failed"
    fi
    # copyout
    mkdir -p "$BUILD_ROOT$TOPDIR/DOCKER"
    local out
    for out in $DAPPER_OUTPUT ; do
	local f="$out"
	test "${f#/}" = "$f" && f="$DAPPER_SOURCE$f"
	local outdir="$TOPDIR/DOCKER/${out#/}"
	outdir="${outdir%/*}"
	mkdir -p "$BUILD_ROOT$outdir"
	$DOCKER_CMD cp "build-dapper-run:$f" "$outdir"
    done
}

# Variables:
# $BUILD_ROOT is the chroot
# $TOPDIR/SOURCES includes the docker sources
# $TOPDIR/$DOCKERIMAGE_ROOT where docker will be called
# $RECIPEFILE the name of the Dockerfile

recipe_build_docker() {
    touch $BUILD_ROOT/etc/resolv.conf

    base_image_path=
    base_image_tag=$(grep "^\s*FROM" "$RECIPEFILE" | head -n 1 | cut -d" " -f2)

    if test "$base_image_tag" != scratch ; then
        base_image_path=$(find containers -name \*.tgz -print -quit -o -name \*.tar -print -quit -o -name \*.tar.\[gx\]z -print -quit)
        test -n "$base_image_path" -a -f "$base_image_path" || cleanup_and_exit 1 "base image not found"
    fi

    mkdir -p "$BUILD_ROOT/$TOPDIR/SOURCES/repos"
    if test "$BUILDENGINE" = podman; then
        DOCKER_TOOL=podman
        DOCKER_CMD="$BUILD_DIR/call-podman --root $BUILD_ROOT"
    else
        DOCKER_TOOL=docker
        DOCKER_CMD="chroot $BUILD_ROOT docker"
        if ! $BUILD_DIR/startdockerd --root "$BUILD_ROOT" --webserver "$TOPDIR/SOURCES/repos" --webserver-upload "$TOPDIR/SOURCES/repos/UPLOAD" ; then
             cleanup_and_exit 1
        fi
    fi
    DOCKERD_STARTED=true

    if test "$BUILDENGINE" = podman; then
	sed -e "s!^DATA_DIR=!DATA_DIR=$TOPDIR/SOURCES/repos!" <"$BUILD_DIR/obs-docker-support" >"$BUILD_ROOT/$TOPDIR/SOURCES/.obs-docker-support"
    else
	cp $BUILD_DIR/obs-docker-support "$BUILD_ROOT/$TOPDIR/SOURCES/.obs-docker-support"
    fi
    chmod 755 "$BUILD_ROOT/$TOPDIR/SOURCES/.obs-docker-support"

    for base_image_path in $(find containers -name \*.tgz -print -o -name \*.tar -print -o -name \*.tar.\[gx\]z -print) ; do
	echo "Loading base image ${base_image_path##*/}"
	if test -L "$base_image_path" ; then
	    # copy into build root
	    cp -L "$base_image_path" "$base_image_path.lnk"
	    mv "$base_image_path.lnk" "$base_image_path"
	fi

	# Inspect the content of the image to decide if this is a layered image
	# or a filesystem one. We need to know if we will "docker load" it or
	# "docker import" it.
	if tar -tf $base_image_path | grep -q "^manifest.json" ; then
	    $DOCKER_CMD load --input $TOPDIR/SOURCES/$base_image_path
	else
	    echo "Filesystem image found"
	    base_image_tag=$(grep "^\s*FROM" "$RECIPEFILE" | head -n 1 | cut -d" " -f2)
	    $DOCKER_CMD import $TOPDIR/SOURCES/$base_image_path "$base_image_tag"
	fi
    done

    # Prepare the package repository
    rm -rf "$BUILD_ROOT/$TOPDIR/SOURCES/repos/UPLOAD"
    if chroot $BUILD_ROOT test -x /usr/bin/createrepo ; then
	chroot $BUILD_ROOT createrepo "$TOPDIR/SOURCES/repos" >/dev/null
    fi
    if chroot $BUILD_ROOT test -x /usr/bin/dpkg-scanpackages ; then
	chroot $BUILD_ROOT bash -c "cd $TOPDIR/SOURCES/repos && dpkg-scanpackages -m . | gzip > Packages.gz"
    fi
    if chroot $BUILD_ROOT test -x /sbin/apk ; then
	apk_arch=$(chroot $BUILD_ROOT apk --print-arch)
	test -n "$apk_arch" || cleanup_and_exit 1 "could not determine apk arch"
	mkdir -p "$BUILD_ROOT/$TOPDIR/SOURCES/repos/$apk_arch"
	find "$BUILD_ROOT/$TOPDIR/SOURCES/repos" -name \*.apk -print | while read apk ; do
	    apk_cn=$(perl -I$BUILD_DIR -MBuild::Apk -e 'print Build::Apk::canonname($ARGV[0])' $apk)
	    test -n "$apk_cn" && ln -f $apk "$BUILD_ROOT/$TOPDIR/SOURCES/repos/$apk_arch/$apk_cn"
	done
	chroot $BUILD_ROOT bash -c "cd $TOPDIR/SOURCES/repos/$apk_arch && apk index --allow-untrusted --rewrite-arch $apk_arch *.apk" > "$BUILD_ROOT/$TOPDIR/SOURCES/repos/$apk_arch/APKINDEX.tar.gz"
    fi
    mkdir -p "$BUILD_ROOT/$TOPDIR/SOURCES/repos/UPLOAD"

    # Prepare the webcache
    mkdir -p "$BUILD_ROOT/$TOPDIR/SOURCES/repos/build-webcache"
    for i in "$BUILD_ROOT/$TOPDIR/SOURCES/build-webcache"-* ; do
	test -e "$i" && ln -f "$i" "$BUILD_ROOT/$TOPDIR/SOURCES/repos/build-webcache/${i##*/build-webcache-}"
    done

    # exclude repos/containers directory
    echo containers >> "$BUILD_ROOT/$TOPDIR/SOURCES/.dockerignore"
    echo repos >> "$BUILD_ROOT/$TOPDIR/SOURCES/.dockerignore"

    # find tags, first look into recipe file
    FIRSTTAG=
    ALLTAGS=
    args=()
    test -n "$RELEASE" && args=("${args[@]}" --release "$RELEASE")
    for t in $(perl -I$BUILD_DIR -MBuild::Docker -e Build::Docker::show -- "${args[@]}" "$BUILD_ROOT/$TOPDIR/SOURCES/$RECIPEFILE" containertags) ; do
	test -n "$FIRSTTAG" || FIRSTTAG="$t"
        ALLTAGS="$ALLTAGS $t"
    done
    # if we did not find a tag, look info a file called TAG
    if test -z "$FIRSTTAG" -a -f TAG ; then
	for t in $(grep -E -v '^#' TAG) ; do
	    test -n "$FIRSTTAG" || FIRSTTAG="$t"
	    ALLTAGS="$ALLTAGS $t"
	done
    fi
    if test "$RECIPEFILE" = Dockerfile.dapper ; then
	FIRSTTAG=build-dapper
	ALLTAGS=build-dapper
    fi
    if test -z "$FIRSTTAG" ; then
        cleanup_and_exit 1 "Please specify a tag for the container"
    fi
    ALLTAGS="${ALLTAGS# }"

    tagargs=()
    for t in $ALLTAGS; do
	tagargs[${#tagargs[@]}]='-t'
	tagargs[${#tagargs[@]}]="$t"
    done

    buildargs=()
    local targetstage=$(queryrecipe --dist "$BUILD_DIST" --configdir "$CONFIG_DIR" --archpath "$BUILD_ARCH" "$BUILD_ROOT/$TOPDIR/SOURCES/$RECIPEFILE" docker_targetstage)
    if test -n "$targetstage" ; then
	buildargs[${#buildargs[@]}]="--target=$targetstage"
    fi
    while read kv ; do
	case "$kv" in
	    [a-zA-Z0-9_]*=*)
		buildargs[${#buildargs[@]}]='--build-arg'
		buildargs[${#buildargs[@]}]="$kv"
		;;
	esac
    done < <( queryconfig --dist "$BUILD_DIST" --configdir "$CONFIG_DIR" --archpath "$BUILD_ARCH" --decode buildflags+ dockerarg )

    if test -n "$BUILD_FLAVOR" ; then
	buildargs[${#buildargs[@]}]='--build-arg'
	buildargs[${#buildargs[@]}]="BUILD_FLAVOR=$BUILD_FLAVOR"
    fi

    # podman special
    if test "$BUILDENGINE" = podman ; then
        buildargs[${#buildargs[@]}]='--pull=never'
        if test "${BUILD_HOST_ARCH#armv8}" != "$BUILD_HOST_ARCH"; then
            # buildah insists on "v8" variant otherwise
	    case "$BUILD_ARCH" in
		armv7*) buildargs[${#buildargs[@]}]='--variant=v7' ;;
		armv6*) buildargs[${#buildargs[@]}]='--arch=arm' ;;	# resets the variant
	    esac
        fi
    fi

    # patch in obs-docker-support helper
    patchdockerfile < "$BUILD_ROOT/$TOPDIR/SOURCES/$RECIPEFILE" > "$BUILD_ROOT/$TOPDIR/SOURCES/.$RECIPEFILE" && \
       mv "$BUILD_ROOT/$TOPDIR/SOURCES/.$RECIPEFILE" "$BUILD_ROOT/$TOPDIR/SOURCES/$RECIPEFILE"
    test -n "$DISTURL" && echo "LABEL org.openbuildservice.disturl=$DISTURL" >> "$BUILD_ROOT/$TOPDIR/SOURCES/$RECIPEFILE"

    # setup binfmt handler for cross builds
    if test -n "$ABUILD_TARGET" ; then
	local target="${ABUILD_TARGET%%-*}"
	if test -n "$target" && ! check_native_arch "$target" ; then
	    local initvm=/usr/lib/build/initvm.`uname -m`
	    if test -x $initvm ; then
		echo "setting up cross building support"
		$initvm
	    else
		echo "warning: $initvm is missing, cross building may not work"
	    fi
	fi
    fi

    # now do the build
    squashopt=--squash
    if test -n "$(perl -I$BUILD_DIR -MBuild::Docker -e Build::Docker::show -- "$BUILD_ROOT/$TOPDIR/SOURCES/$RECIPEFILE" nosquash)" ; then
	squashopt=
	echo "Building image $ALLTAGS (nosquash)"
    else
	echo "Building image $ALLTAGS"
    fi

    if test "$BUILDENGINE" = podman; then
	test -n "$squashopt" && squashopt="--layers=false"
        buildformat=$(queryconfig --dist "$BUILD_DIST" --configdir "$CONFIG_DIR" --archpath "$BUILD_ARCH" buildflags container-build-format)
        formatopt="--format docker"
        if test "$buildformat" = oci; then
            formatopt="--format oci"
        fi
	if ! $DOCKER_CMD build $formatopt $squashopt -v "$TOPDIR/SOURCES/repos:$TOPDIR/SOURCES/repos" --network=host "${tagargs[@]}" "${buildargs[@]}" -f "$TOPDIR/SOURCES/$RECIPEFILE" $TOPDIR/SOURCES/ ; then
	    cleanup_and_exit 1 "$DOCKER_TOOL build command failed"
	fi
    else
	if ! $DOCKER_CMD build $squashopt --network=host "${tagargs[@]}" "${buildargs[@]}" -f "$TOPDIR/SOURCES/$RECIPEFILE" $TOPDIR/SOURCES/ ; then
	    cleanup_and_exit 1 "$DOCKER_TOOL build command failed"
	fi
    fi

    if test "$RECIPEFILE" = Dockerfile.dapper ; then
	dapper_run "$FIRSTTAG"
	recipe_cleanup_docker
	BUILD_SUCCEEDED=true
	return
    fi

    # Check for OS setting
    GOOS=`$DOCKER_CMD image inspect "$FIRSTTAG" --format '{{ .Os }}'`

    # Save the resulting image to a tarball. Use first tag for generating the file name if name or version is unknown.
    mkdir -p $BUILD_ROOT$TOPDIR/DOCKER
    FILENAME=$(perl -I$BUILD_DIR -MBuild::Docker -e Build::Docker::show -- "$BUILD_ROOT/$TOPDIR/SOURCES/$RECIPEFILE" filename)
    if test -z "$FILENAME" ; then
	# check if there is a version defined. if yes, keep the repository part and overwrite the tag
	FILENAME=$(perl -I$BUILD_DIR -MBuild::Docker -e Build::Docker::show -- "$BUILD_ROOT/$TOPDIR/SOURCES/$RECIPEFILE" version)
	if test -n "$FILENAME" ; then
	    # append the flavor to the version
	    test -n "$BUILD_FLAVOR" && FILENAME="$FILENAME-$BUILD_FLAVOR"
	    FILENAME="${FIRSTTAG%%:*}:$FILENAME"
	else
	    FILENAME="$FIRSTTAG"
	fi
    fi
    FILENAME="${FILENAME//[\/:]/-}"
    test -n "$GOOS" -a "$GOOS" != "linux" && FILENAME="${FILENAME}_$GOOS"
    FILENAME="$FILENAME.${BUILD_ARCH%%:*}"
    test -n "$RELEASE" && FILENAME="$FILENAME-$RELEASE"
    echo "Saving image $FIRSTTAG to $FILENAME.tar"
    local compression_format
    if test "$BUILDENGINE" = podman; then
	compression_format=$(queryconfig --dist "$BUILD_DIST" --configdir "$CONFIG_DIR" --archpath "$BUILD_ARCH" buildflags container-compression-format)
    fi
    if test "$BUILDENGINE" = podman -a -n "$compression_format" -a "$compression_format" != gzip ; then
	if ! $DOCKER_CMD push --compression-format="$compression_format" "$FIRSTTAG" "oci-archive:$TOPDIR/DOCKER/$FILENAME.tar"; then
	    cleanup_and_exit 1 "$DOCKER_TOOL push command failed"
	fi
    else
	if ! $DOCKER_CMD save --output "$TOPDIR/DOCKER/$FILENAME.tar" "$FIRSTTAG" ; then
	    cleanup_and_exit 1 "$DOCKER_TOOL save command failed"
	fi
    fi

    # Create containerinfo
    args=()
    test -n "$DISTURL" && args=("${args[@]}" --disturl "$DISTURL")
    test -n "$RELEASE" && args=("${args[@]}" --release "$RELEASE")
    test -s "containers/annotation" && args=("${args[@]}" --annotationfile containers/annotation)
    perl -I$BUILD_DIR -MBuild::Docker -e Build::Docker::showcontainerinfo -- "${args[@]}" "$BUILD_ROOT/$TOPDIR/SOURCES/$RECIPEFILE" "$FILENAME.tar" "$ALLTAGS" > "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.containerinfo"

    if test -f "$BUILD_ROOT/$TOPDIR/SOURCES/repos/UPLOAD/packages" ; then
	# copy over .packages files
	for i in basepackages packages ; do
	    test -f "$BUILD_ROOT/$TOPDIR/SOURCES/repos/UPLOAD/$i" && cp "$BUILD_ROOT/$TOPDIR/SOURCES/repos/UPLOAD/$i" "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.$i"
	done
	if test -e "$BUILD_ROOT/$TOPDIR/SOURCES/repos/.pkgsummaries" ; then
	    for i in pkgsummaries ; do
		test -f "$BUILD_ROOT/$TOPDIR/SOURCES/repos/UPLOAD/$i" && cp "$BUILD_ROOT/$TOPDIR/SOURCES/repos/UPLOAD/$i" "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.$i"
	    done
	fi
    else
	# build did not create packages file, try introspection
	cp --remove-destination "$BUILD_DIR/create_container_package_list" "$BUILD_ROOT/tmp/create_container_package_list"
	echo "creating package information"
	chroot "$BUILD_ROOT" /bin/bash /tmp/create_container_package_list "$TOPDIR/DOCKER/$FILENAME.tar" > "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.packages"
	if test -e "$BUILD_ROOT/$TOPDIR/SOURCES/repos/.pkgsummaries" ; then
	    echo "creating package summaries information"
	    chroot "$BUILD_ROOT" /bin/bash /tmp/create_container_package_list --summaries "$TOPDIR/DOCKER/$FILENAME.tar" > "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.pkgsummaries"
	fi
	local basecontainer=$(perl -I$BUILD_DIR -MBuild::Docker -e Build::Docker::show -- "$BUILD_ROOT/$TOPDIR/SOURCES/$RECIPEFILE" basecontainer)
	if test -n "$basecontainer" ; then
	    echo "creating base package information for base container $basecontainer"
	    $DOCKER_CMD save --output "/tmp/basecontainer.tar" "$basecontainer"
	    chroot "$BUILD_ROOT" /bin/bash /tmp/create_container_package_list "/tmp/basecontainer.tar" > "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.basepackages"
	fi
	rm -f "$BUILD_ROOT/tmp/create_container_package_list"
    fi
    rm -rf "$BUILD_ROOT/$TOPDIR/SOURCES/repos/UPLOAD"

    # create sbom if requested
    for format in $(queryconfig --dist "$BUILD_DIST" --configdir "$CONFIG_DIR" --archpath "$BUILD_ARCH" buildflags+ sbom | sort -u) ; do
	echo "Generating $format sbom file"
	generate_sbom --format "$format" --container-archive "$BUILD_ROOT/$TOPDIR/DOCKER/$FILENAME.tar" > "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.${format/cyclonedx/cdx}.json" || cleanup_and_exit 1 "generate_sbom failed!"
	test -s "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.${format/cyclonedx/cdx}.json" || rm -f "$BUILD_ROOT$TOPDIR/DOCKER/$FILENAME.${format/cyclonedx/cdx}.json"
    done
 
    # We're done. Clean up.
    recipe_cleanup_docker
    BUILD_SUCCEEDED=true
}

recipe_resultdirs_docker() {
    echo DOCKER
}

recipe_cleanup_docker() {
    if test -n "$DOCKERD_STARTED" ; then
	test -n "$TOPDIR" && rm -f "$BUILD_ROOT/$TOPDIR/SOURCES/.obs-docker-support"
	DOCKERD_STARTED=
	$BUILD_DIR/startdockerd --root "$BUILD_ROOT" --kill
    fi
}

# Local Variables:
# mode: Shell-script
# End:
