#!/usr/bin/env bash
# ======================================================================
#
# API CLIENT :: GET A CI FILE FROM PACKAGE SERVER
#
# Source: https://git-repo.iml.unibe.ch/iml-open-source/imldeployment-client/
# ----------------------------------------------------------------------
# 2021-03-31  v1.0  <axel.hahn@iml.unibe.ch>  init
# 2021-04-13  v1.1  <axel.hahn@iml.unibe.ch>  add support for custom config
# 2021-04-15  v1.2  <axel.hahn@iml.unibe.ch>  added debugging of curl request
# 2021-10-14  v1.3  <axel.hahn@iml.unibe.ch>  add nanoseconds in hashed base data
# 2023-02-14  v1.4  <axel.hahn@unibe.ch>      compatibility to openssl v3 
# 2023-09-15  v1.5  <axel.hahn@unibe.ch>      remove temp file after download of multiple files
# ======================================================================

# ----------------------------------------------------------------------
# CONFIG
# ----------------------------------------------------------------------

version="v1.5"
about="CI PACKAGE GETTER $version;
(c) 2021 Institute for Medical Education (IML); University of Bern;
GNU GPL 3.0"

line="----------------------------------------------------------------------"
bDebug=0
customconfig=

. $0.cfg

# ----------------------------------------------------------------------
# FUNCTIONS
# ----------------------------------------------------------------------

function showhelp(){
self=$( basename $0 )
echo "$line
$about
$line

Get packages from a software sattelite of IML ci server.

SYNTAX:

  $self [OPTIONS]

OPTIONS:

  -h          Show this help
  -v          Show version

  -c CFGFILE  load custom config file after defaults in $self.cfg
  -d          enable debug infos
  -e PHASE    phase; overrides env variable IMLCI_PHASE
  -f FILE     filename to get (without path); overrides env variable IMLCI_FILE
  -l ITEM     list
  -o OUTFILE  optional output file
  -p PROJECT  ci project id; overrides env variable IMLCI_PROJECT
  -s SECRET   override secret in IMLCI_PKG_SECRET
  -u URL      URL of iml ci server without trailing /; overrides env variable IMLCI_URL
  
VALUES:

  CFGFILE     custom config file. It is useful to handle files of different 
              projects on a server.
  PHASE       is a phase of the ci server; one of preview|stage|live
  FILE        is a filename without path that was created by ci server.
  OUTFILE     Output file. It can countain a path. If none is given the filename
              will be taken from FILE and stored in current directory
  PROJECT     project id of the ci server
  SECRET      secret to access project data on package server. Your given secret
              must match the secret on package server to get access to any url.
  ITEM        type what to list; one of phases|projects|files
              To list projects a phase must be set.
              To list files a phase and a project must be set.

DEFAULTS:

  You don't need to set all values by command line. Use a config to set defaults
  $0.cfg

EXAMPLES:

  If url, secret, project and phase are set in the config you can operate by
  setting the filename to request.

  $self -f FILE 
    downloads FILE to the current dir.

  $self -f FILE -o my-own-filename.tgz 
    downloads FILE as my-own-filename.tgz

  $self -f ALL 
    there is a special file ALL; it fetches all filenames by executing a directory 
    listing and then downloads all remote files with their original name

  $self -e preview -l projects
    list existing projects in phase preview

  $self -l files
    list existing files of current project

  Remark: The directory listing can be turned off on the package server and
  results in a 403 status.
"
}

# make an http request to fetch the software
#
# param  string  method; should be GET
# param  string  request url (without protocol and server)
# param  string  optional: filename for output data
# param  string  optional: secret; default: it will be generated
#
# global int     bDebug  (0|1)
# global string  line    string for a line with dashes
function makeRequest(){

  local apiMethod=$1
  local apiRequest=$2
  local outfile=$3
  local secret=$4

  # local outfile=$( mktemp )

  if [ $bDebug = 1 ]; then
    echo $line
    echo "$apiMethod ${apiHost}${apiRequest}"
    echo $line
  fi

  if [ ! -z "$secret" ]; then

    # --- date in http format
    LANG=en_EN
    # export TZ=GMT
    apiTS=$(date "+%a, %d %b %Y %H:%M:%S.%N %Z")


# --- generate data to hash: method + uri + timestamp; delimited with line break
data="${apiMethod}
${apiRequest}
${apiTS}
"
    # these ase non critical data ... it does not show the ${secret}
    if [ "$bDebug" = "1" ]; then
        echo "RAW data for hashed secret:"
        echo "$data"
    fi

    # generate hash - split in 2 commands (piping "cut" sends additional line break)
    myHash=$(echo -n "$data" | openssl dgst -sha1 -hex -hmac "${secret}" | cut -f 2 -d " ")
    myHash=$(echo -n "$myHash" | base64)

    moreheaders="--fail"
    test $bDebug = 1 && moreheaders="-i"

    tmpdownloadfile="${outfile}.downloading"

    curl \
      -H "Accept: application/json" -H "Content-Type: application/json" \
      -H "Date: ${apiTS}" \
      -H "Authorization: bash-client:${myHash}" \
      -X $apiMethod \
      -o "${tmpdownloadfile}" \
      $moreheaders \
      -s \
      ${IMLCI_URL}${apiRequest}

    rc=$?
    if [ "$bDebug" = "1" ]; then
        cat "${tmpdownloadfile}"
        rm -f "${tmpdownloadfile}"
        exit 0
    fi

    if [ $rc -eq 0 ]; then
        # echo OK.

        # no outfile (= request to a directory)
        if [ -z "$outfile" ]; then
            # echo
            # echo ----- RESPONSE BODY:
            cat "${tmpdownloadfile}"
            rm -f "${tmpdownloadfile}" 
        else
            mv "${tmpdownloadfile}" "${outfile}"
            ls -l "${outfile}"
        fi
    else
        echo ERROR: Download failed.
        exit 1
    fi
  else
    curl\
      -H "Accept: application/json" -H "Content-Type: application/json" \
      -X $apiMethod \
      -o "${tmpdownloadfile}" \
      ${IMLCI_URL}${apiRequest}
  fi

}


# ----------------------------------------------------------------------
# MAIN
# ----------------------------------------------------------------------

if  [ $# -lt 1 ]; then
  showhelp
  exit 1
fi


while getopts "c:de:f:hl:o:p:s:u:v" option; do 
    case ${option} in
      c) customconfig="$OPTARG" ;;
      d) bDebug=1 ;;
      e) export IMLCI_PHASE=$OPTARG ;;
      f) export IMLCI_FILE=$OPTARG ;;
      h) showhelp
         exit 0
         ;;
      l) case $OPTARG in
            phases)
                IMLCI_PHASE=''
                IMLCI_PROJECT=''
                IMLCI_FILE=''
                ;;
            projects)
                IMLCI_PROJECT=''
                IMLCI_FILE=''
                ;;
            files)
                IMLCI_FILE=''
                ;;
            *)
                echo ERROR: invalid value for option [-l]
                echo
                showhelp
                exit 2
         esac
            ;;
      o) export IMLCI_OUTFILE=$OPTARG ;;
      p) export IMLCI_PROJECT=$OPTARG ;;
      s) export IMLCI_PKG_SECRET=$OPTARG ;;
      u) export IMLCI_URL=$OPTARG ;;
      v) echo $about; exit 0 ;;
      *)
        echo ERROR: invalid option [${option}]
        echo
        showhelp
        exit 2
    esac
done

if [ ! -z "$customconfig" ]; then
    if [ -r "$customconfig" ]; then
        . "$customconfig" || exit 2
     else
        echo "ERROR: unable to read custom config [$customconfig]."
        exit 2
     fi
fi

test -z ${IMLCI_OUTFILE} && IMLCI_OUTFILE=$IMLCI_FILE

if [ $bDebug = 1 ]; then
    pre=">>>>>> "
    echo $line
    echo
    echo DEBUG INFOS
    echo
    echo "${pre} defaults in $0.cfg"
    cat $0.cfg 2>/dev/null
    echo
    if [ ! -z "$customconfig" ]; then 
        echo "${pre} custom config $customconfig"
        cat "$customconfig"
        echo
    fi
    echo "${pre} Params (override default values)"
    echo $*
    echo
    echo "${pre} effective values"
    echo "IMLCI_URL = $IMLCI_URL"
    echo "IMLCI_PKG_SECRET = $IMLCI_PKG_SECRET"
    echo "IMLCI_PROJECT = $IMLCI_PROJECT"
    echo "IMLCI_PHASE = $IMLCI_PHASE"
    echo "IMLCI_FILE = $IMLCI_FILE"
    echo "IMLCI_OUTFILE = $IMLCI_OUTFILE"

    echo
fi

if [ "$IMLCI_FILE" = "ALL" ]; then
    # echo ALL files were requested ...
    printf "%-30s" "get list of all files... "
    tmpfilelist=$( mktemp )
    $0 -u "${IMLCI_URL}" \
        -p "${IMLCI_PROJECT}" \
        -e "${IMLCI_PHASE}" \
        -s "${IMLCI_PKG_SECRET}" \
        -l files \
        -o "${tmpfilelist}"
    
    # cat "${tmpfilelist}"
    cat "${tmpfilelist}" | grep "^file:" | while read fileline
    do
        # echo $line
        myfile=$( echo $fileline | cut -f 2- -d ':' )
        printf "%-30s" "GET $myfile... "
        $0 -u "${IMLCI_URL}" \
            -p "${IMLCI_PROJECT}" \
            -e "${IMLCI_PHASE}" \
            -s "${IMLCI_PKG_SECRET}" \
            -f "${myfile}"
    done
    rm -f "${tmpfilelist}"
else 
    makeRequest GET "/packages/$IMLCI_PHASE/$IMLCI_PROJECT/$IMLCI_FILE" "$IMLCI_OUTFILE" "$IMLCI_PKG_SECRET"
fi