#!/bin/bash
# SPDX-License-Identifier: EPL-1.0
##############################################################################
# Copyright (c) 2017 The Linux Foundation and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
##############################################################################

# Sign artifacts with gpg or sigul

# Sigul signing requires the following:
#
# * sigul is installed
# * configuration file for sigul with the full file path to the file as an
#   environment variable of $SIGUL_CONFIG
# * the sigul user (key) as an environment variable of $SIGUL_KEY
# * the sigul nss password, null terminated in a file referred to by
#   $SIGUL_PASSWORD
#
# If this is being run via JJB and sigul was installed via the scaffolding
# macros then all environment variables should already be properly set

GPG_BIN="gpg"
if hash gpg2 2>/dev/null; then
    GPG_BIN="gpg2"
fi


sign() {
    # Entry point for the deploy command.
    subcommand=$1; shift

    case "$subcommand" in
        dir )
            echo "Signing Directory..."
            sign_dir 'gpg' "$@"
            exit 0
            ;;
        nexus )
            echo "Signing Nexus repo..."
            sign_nexus "$@"
            exit 0
            ;;
        sigul )
            echo "Signing Directory with Sigul..."
            sign_dir 'sigul' "$@"
            exit 0
            ;;
        git-tag )
            echo "Signing Git tag with Sigul..."
            sign_git_tag "$@"
            exit 0
            ;;
        container )
            echo "Signing container with Sigul..."
            sign_container "$@"
            exit 0
            ;;
        * )
            echo "Invalid command: $subcommand" 1>&2
            exit 1
            ;;
    esac
}


sign_git_tag() {
    # Signs the specified git tag.
    #
    # Parameters:
    #
    #     <tag>: The name of the git tag to be signed.
    local tag="$1"

    echo "Signing $tag"
    sigul --batch -c "$SIGUL_CONFIG" sign-git-tag \
        "$SIGUL_KEY" "$tag" < "$SIGUL_PASSWORD"
}


sign_container() {
    # Signs the specified Docker container.
    #
    # Parameters:
    #
    #     <manifest>: Manifest of the container to be signed.
    #     <tag>: The container's tag.
    local manifest="$1"
    local tag="$2"

    echo "Signing $manifest:$tag"
    sigul --batch -c "$SIGUL_CONFIG" sign-container -o "$manifest-$tag.asc" \
        "$SIGUL_KEY" "$manifest" "$tag" < "$SIGUL_PASSWORD"
}


sign_dir() {
    # GPG signs all of the files in a directory
    #
    # Parameters:
    #
    #     <signer>: gpg|sigul
    #     <directory>: The directory to find and sign artifacts.
    #     <mode>: serial|parallel
    local signer="$1"
    local dir="$2"

    if [ -z "$2" ]; then
        echo "Missing required arguments."
        echo "Usage: $0 sign-dir 'gpg|sigul' <directory>"
        exit 1
    fi

    if [ "$signer" == "gpg" ]; then
        test_gpg_key
    fi

    set -e  # Fail immediately if any if signing fails
    mapfile -t files_to_sign < <(find "$dir" -type f ! -name "*.asc" \
        ! -name "*.md5" \
        ! -name "*.sha1" \
        ! -name "_maven.repositories" \
        ! -name "_remote.repositories" \
        ! -name "*.lastUpdated" \
        ! -name "maven-metadata-local.xml" \
        ! -name "maven-metadata.xml")

    if [ "${#files_to_sign[@]}" -eq 0 ]; then
        echo "ERROR: No files to sign. Quitting..."
        exit 1
    fi

    local mode="serial"
    if hash parallel 2>/dev/null; then
        # Unless you specifically asked for serial mode
        if [ "$3" != "serial" ]; then
            local mode="parallel"
        fi
    fi

    if [ "$mode" == "parallel" ]; then
        echo "Signing in parallel..."
        case "$signer" in
            gpg )
                # We need to wordsplit ${files_to_sign[*]} so disable quoting check
                # shellcheck disable=SC2086
                parallel --no-notice --jobs 200% --halt now,fail=1 \
                    "$GPG_BIN --batch -abq {}" ::: "${files_to_sign[@]}"
                ;;
            sigul )
                # We need to wordsplit ${files_to_sign[*]} so disable quoting check
                # shellcheck disable=SC2086
                parallel --no-notice --jobs 200% --retries 3 \
                    "sigul --batch -c $SIGUL_CONFIG sign-data -a -o {}.asc \
                    $SIGUL_KEY {} < $SIGUL_PASSWORD" ::: "${files_to_sign[@]}"
                ;;
            * )
                echo "Invalid signer: $signer" 1>&2
                exit 1
                ;;
        esac

        echo "Signed the following files:"
        printf '%s\n' "${files_to_sign[@]}"
    else
        echo "Signing in serial mode..."
        case "$signer" in
            gpg )
                for f in "${files_to_sign[@]}"; do
                    echo "Signing $f"
                    "$GPG_BIN" --batch -abq "$f"
                done
                ;;
            sigul )
                for f in "${files_to_sign[@]}"; do
                    echo "Signing $f"
                    sigul --batch -c "$SIGUL_CONFIG" sign-data -a -o "$f.asc" \
                        "$SIGUL_KEY" "$f" < "$SIGUL_PASSWORD"
                done
                ;;
            * )
                echo "Invalid signer: $signer" 1>&2
                exit 1
                ;;
        esac
    fi
    set +e
}


sign_nexus_usage () {
    echo "Usage: $0 nexus <nexus_repo_url>"
    echo ""
    echo "    nexus_repo_url:   The URL to the Nexus repository to be signed."
    echo "        Ex: https://nexus.opendaylight.org/content/repositories/autorelease-1888"
    echo ""
    echo "Options:"
    echo "    -d /path/to/store/signatures"
    echo "    -w gpg|sigul"
}


sign_nexus() {
    # Fetch and sign Nexus repo using GPG or sigul
    #
    # The resultant output of this command is stored in
    # /tmp/gpg-signatures.XXXXXXXXXX unless the -d parameter is passed to
    # override.
    local signer='gpg'
    while getopts d:m:w:h o; do
      case "$o" in
        h)
            sign_nexus_usage
            exit 0
            ;;

        d)
            local sign_dir="$OPTARG"
            mkdir -p "$sign_dir"
            ;;

        m)
            local sign_mode="$OPTARG"
            ;;

        w)
            if [[ "$OPTARG" =~ ^(gpg|sigul)$ ]]
            then
                signer="$OPTARG"
            else
                sign_nexus_usage
                exit 1
            fi
            ;;

        [?])
            sign_nexus_usage
            exit 1
            ;;
      esac
    done
    shift $((OPTIND-1))

    local nexus_repo_url="$1"

    if [ -z "$1" ]; then
        echo "Missing required arguments."
        sign_nexus_usage
        exit 1
    fi

    # Ensure that the repo_url has a trailing slash as wget needs it to work
    case "$nexus_repo_url" in
        */)
            ;;
        *)
            nexus_repo_url="$nexus_repo_url/"
            ;;
    esac

    sign_dir=${sign_dir:-$(mktemp -d /tmp/gpg-signatures.XXXXXXXXXX)}
    cd "$sign_dir" || exit 1

    echo "Fetching artifacts from $nexus_repo_url to $sign_dir..."
    if ! wget -nv --recursive --execute robots=off --no-parent \
              --no-host-directories --cut-dirs=3 --level=15 \
              --continue --no-remove-listing \
              "$nexus_repo_url"; then
        echo "ERROR: Failed to download artifacts."
        exit 1
    fi

    echo "Removing files that do not need to be cloned..."
    mapfile -t remove_files < <(find . -type f -name "index.html" \
        -o -name "*.asc" \
        -o -name "*.md5" \
        -o -name "*.sha1" \
        -o -name "_maven.repositories*" \
        -o -name "_remote.repositories*" \
        -o -name "maven-metadata-local.xml*" \
        -o -name "maven-metadata.xml*" \
        -o -name "archetype-catalog.xml")
    for f in "${remove_files[@]}"; do
        rm "$f"
    done

    echo "Signing artifacts..."
    if [ "$signer" == "gpg" ]
    then
        sign_dir 'gpg' "$sign_dir" "$sign_mode"
    else
        sign_dir 'sigul' "$sign_dir" "$sign_mode"
    fi

    echo "Removing non-signature files..."
    mapfile -t remove_files < <(find . -type f -not -name '*.asc')
    for f in "${remove_files[@]}"; do
        rm "$f"
    done

    echo "Printing signatures generated"
    find . -type f

    echo "Signing complete. Signatures can be found in $sign_dir"
}


test_gpg_key() {
    # Test that our GPG key works by signing a test file.

    local test_file
    test_file=$(mktemp)
    echo "Test Signature" > "$test_file"
    "$GPG_BIN" --batch -abq "$test_file"
    if ! "$GPG_BIN" --verify "$test_file".asc "$test_file" > /dev/null 2>&1; then
        echo "ERROR: Failed to validate signature."
        rm "$test_file" "$test_file".asc  # Cleanup before we exit
        exit 1
    fi
    rm "$test_file" "$test_file".asc  # Cleanup tmp files
}


# Only run the script if it is being called directly and not sourced.
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]
then
    sign "$@"
fi
