#!/bin/echo 'This is function library. To use: source $OPENSHIFT_CARTRIDGE_SDK_BASH'; exit 1

# This is a library of functions for OpenShift cartridge authors.
# To use this library add 'source $OPENSHIFT_CARTRIDGE_SDK_BASH' to your bash script.

[ ${OO_BASH_SDK:-false} == true ] && return 0
OO_BASH_SDK=true

# report message on stderr and exit with provided exit status
function error {
    echo "$1" 1>&2
    exit "$2"
}

# report message on stderr
function warning {
    echo "$1" 1>&2
}

# report text to application developer
# Argument(s):
# - Text to be displayed on success
function client_result {
    client_out "CLIENT_RESULT" "$1"
}

# report text to application developer
# Argument(s):
# - Text will always be displayed. Used to notify application developer of a transient issue.
function client_message {
    client_out "CLIENT_MESSAGE" "$1"
}

# report text to application developer as error
# Argument(s):
# - Text will be displayed when there is an error
function client_error {
    client_out "CLIENT_ERROR" "$1"
}

# report text to application developer as error in your code
# Will be displayed...
function client_internal_error {
    client_out "CLIENT_INTERNAL_ERROR" "$1"
}

# report text to application developer as debugging information
function client_debug {
    client_out "CLIENT_DEBUG" "$1"
}

# format text for reporting to application developer
#
# Argument(s):
# - type of message, will be prefix for each line in text
# - text to be processed
function client_out() {
    local type=$1
    local output=$2
    local IFS_BAK=$IFS
IFS="
"
    for line in $output
    do
        echo "$type: $line"
    done
    IFS=$IFS_BAK
}

# set application information in Broker data store
# Argument(s):
# - name of attribute to add plus value
function set_app_info {
    echo "APP_INFO: $1"
}

# set cartridge attribute in Broker data store
# Argument(s):
# - name of attribute to add plus value
function send_attr {
    echo "ATTR: $1"
}

function add_domain_ssh_key {
    echo "SSH_KEY_ADD: $1"
}

function add_app_ssh_key {
    echo "APP_SSH_KEY_ADD: $1 $2"
}

# Add environment variable visible to all gears in a domain
# Argument(s):
# - name of environment variable to add plus value
function add_domain_env_var {
    echo "ENV_VAR_ADD: $1"
}

# remove environment variable visible to all gears in application
# Argument(s):
# - name of environment variable to remove
function app_remove_env_var {
    echo "APP_ENV_VAR_REMOVE: $1"
}

function add_broker_auth_key {
    echo "BROKER_AUTH_KEY_ADD: "
}

function remove_broker_auth_key {
    echo "BROKER_AUTH_KEY_REMOVE: "
}

# add cartridge data in Broker data store
# Argument(s):
# - list of cartridge datums
function cart_data {
    echo "CART_DATA: $@"
}

# add cartridge properties in Broker data store
# Argument(s):
# - list of cartridge properties
function cart_props {
    echo "CART_PROPERTIES: $@"
}

# Sets the appropriate env variable files
# Arguments:
#  - Variable to set
#  - Value
#  - Target ENV directory
function set_env_var {
  local var=$1
  local val=$2
  local target=$3

  [[ -z $target || -z $var || -z $val ]] && \
    error "Must provide a variable name, value, and target directory for environment variables" 64
  [ ! -d $target ] && \
    error "Target directory must exist for environment variables" 64

  echo "$val" >"${target}/${var}"
}

# Generate a random string from /dev/urandom
# Arguments:
#  - Desired length (optional)
#  - Possible character space (optional)
#  - Patterns to omit (optional)
function random_string {
  local DEFAULT_SPACE="a-zA-Z0-9"
  local DEFAULT_LEN=12

  local len=${1-$DEFAULT_LEN}
  local space=${2-"${DEFAULT_SPACE}"}
  local omit=${3-""}

  local rnd=$(head -n 50 /dev/urandom | tr -dc $space | fold -w $len)
  [ -n "${omit}" ] && rnd=$(echo "${rnd}" | grep -v "${omit}")
  echo $(echo "${rnd}" | head -n1)
}

# Pad a string with random characters
# Arguments:
#  - String to pad
#  - Desired length
#  - Pattern to pad with (optional)
function pad_string {
  local str=$1
  local len=$2
  local pattern=$3

  local remain=$(( $len - ${#str} ))
  if [ "$remain" -ge 1 ]
  then
    local rnstr=$(random_string $remain $pattern)
    str="${str}${rnstr}"
  fi
  echo $str
}

# Generate a password
# Arguments:
#  - Desired length (optional)
#  - Character space (optional)
#  - Ignore pattern (optional)
function generate_password {
  local DEFAULT_LEN=12
  local DEFAULT_CHAR="a-np-zA-NP-Z1-9-_" #Dash, underscore, Alphanumeric except o,O,0
  local DEFAULT_IGNORE="^-"

  echo $(random_string ${1-$DEFAULT_LEN} ${2-$DEFAULT_CHAR} ${3-$DEFAULT_IGNORE})
}

# Generate a username and pad it to a certain length
# Arguments:
#  - Username (optional)
#  - Desired length (optional)
#  - Pad characters (optional)
function generate_username {
  local DEFAULT_USERNAME='admin'
  local DEFAULT_LEN=12
  local DEFAULT_CHAR="a-np-zA-NP-Z1-9" #Alphanumeric except o,O,0

  echo $(pad_string ${1-$DEFAULT_USERNAME} ${2-$DEFAULT_LEN} ${3-$DEFAULT_CHAR})
}

# wait up to 30 seconds for given process to stop
# Argument(s):
#  - process id to wait on
function wait_for_stop {
    local pid=$1
    for i in {1..60}
    do
        if `ps --pid $pid > /dev/null 2>&1`
        then
            echo "Waiting for stop to finish"
            sleep .5
        else
            break
        fi
    done
}

# report processing running for given user uid.
# Argument(s):
# - user uid    Do not use user login name, all numeric user login names will break ps
function print_user_running_processes {
    local userid=$1
    echo ""
    echo "Running Processes:"
    echo ""
    ps -FCvx -U "${userid}"
    echo ""
}

# Check is a process is running
# Arguments:
#  - Process name
#  - Pidfile
#  - UID to check (optional)
function process_running {
  local process=$1
  local pidfile=$2
  local uid=${3-`id -u`}

  # Check the pidfile for a running process
  {
    if [ -f $pidfile ]; then
      local error=$(pgrep -F $pidfile 2>&1)
      # pgrep returns 0 with an invalid pidfile, so we need to check the output
      [ $? ] && ! [[ "${error}" =~ 'pidfile not valid' ]] && return 0
    fi
  }

  # Check pgrep for the process name and user id
  {
    $(pgrep -x ${process} -u ${uid} > /dev/null 2>&1) && return 0
  }
  return 1
}

function pid_is_httpd() {
    ps -p "$1" 2> /dev/null | grep httpd > /dev/null
}

function killall_matching_httpds() {
   [ -z "$1" ]  &&  return 1
   ps -u `id -u` -o pid,command | grep "$1" | grep -v "grep" |    \
       awk '{ print $1 }' |  xargs kill -9  > /dev/null 2>&1  || :
}

# Attempt to resurrect the Apache PID file if its corrupt.
#  Caution: there may be multiple Apache processes on the gear.
function ensure_valid_httpd_pid_file() {
    local pid_file="$1"
    local cfg_file="$2"

    local force_rebuild=""
    local pid_contents=""
    local pid_regex='^[1-9][0-9]+$'
    local real_pid=""

    if [ -e "$pid_file" ]
    then
        # Is it a file, owned by me and readable?
        if ! [ -f "$pid_file" -a -O "$pid_file" -a -r "$pid_file" ]
        then
            force_rebuild="true"
        else
            # The pid file must contain one and only one number, nothing else or be blank.
            pid_contents=$(cat "$pid_file" 2>/dev/null || :)
            if ! [[ "$pid_contents" =~ $pid_regex ]]
            then
                force_rebuild="true"
            fi
        fi
    fi

    # If we are very lucky, we can find a valid PID to replace corrupt data.
    if [ -n "$force_rebuild" ]
    then
        rm -rf "$pid_file"  # Could be a corrupt inode, a dir, symlink, context or ownership problems, etc...
        if [ "$cfg_file" ]
        then
            real_pid=$(pgrep -o -u `id -u` -f -- "-f $cfg_file" )
            if [ -n "$real_pid" ]
            then
                echo "$real_pid" > "$pid_file"
            fi
        fi
    fi
}

function ensure_valid_httpd_process() {
    # $1 == pidfile.
    # $2 == httpd config file.
    ensure_valid_httpd_pid_file "$1" "$2"
    [ -n "$1" ]  &&  [ -f "$1" ]  &&  pid_is_httpd `cat "$1"`  &&  return 0
    [ -n "$1" ]  &&  rm -f "$1"
    killall_matching_httpds "$2"
}

# application developer has requested a hot deploy, 0 == true, 1 == false
function hot_deploy_marker_is_present() {
    marker_present hot_deploy
}

# report the primary cartridge name for this gear
function primary_cartridge_name() {
  awk -F: '{printf "%s-%s", $2, $3}' $OPENSHIFT_PRIMARY_CARTRIDGE_DIR/env/OPENSHIFT_*_IDENT
}

# Returns 0 if the named marker $1 exists, otherwise 1.
function marker_present() {
  [ -f "${OPENSHIFT_REPO_DIR}/.openshift/markers/$1" ]
}

# Add element(s) to end of path
#
# $1 path
# $2 element(s) to add
# return modified path
function path_append {
    local canon="$(path_remove $1 $2)"
    echo -n "${canon:+"$canon:"}$2"
}

# Add element(s) to front of path
#
# $1 path
# $2 element(s) to add
# return modified path
function path_prepend {
    local canon="$(path_remove $1 $2)"
    echo -n "$2${canon:+":$canon"}"
}

# Remove element(s) from path
#
# $1 path
# $2 element(s) to remove
# return modified path
function path_remove {
  local results="$1"
  for e in $(echo -n "$2" | sed -e 's/:/ /g'); do
    results=$(echo -n "$results" | awk -v RS=: -v ORS=: '$0 != "'$e'"' | sed 's/:$//')
  done
  echo -n "$results"
}

# Update the PassEnv directives in the httpd configuration file
#
# $1 full path to httpd.conf file
function update_httpd_passenv {
  [ -f $1 ] || (client_error "HTTP Configuration for PassEnv failed: $1 missing" && return)

  updated=/tmp/$(basename $1).updated
  ( sed <$1 -e '/^PassEnv /d'
    for key in $(env |cut -d= -f1)
    do
      if [[ $key =~ ^[A-Z].* ]]
      then
        echo PassEnv $key
      fi
    done ) >$updated

  [ -s $updated ] || (client_error "HTTP Configuration for PassEnv failed: update corrupted $1, using old configuration" && return)
  mv $updated $1
}

# Sync repo to the other gears of this app
#
# $1 Flag to indicate whether this is a new gear
# $2 An array of gears to sync
#    Format:  (<gear_uuid>@<ip>:<deploy_cart_type>;<gear_name>-<namespace>.<openshift_domain> <gear_uuid>@<ip>:<deploy_cart_type>;<gear_name>-<namespace>.<openshift_domain>)
#        Ex:  (e86e4bb0e37111e29c0112313d157058@10.85.127.166:php;e86e4bb0e37111e29c0112313d157058-myns.rhcloud.com 51d367f4af94b85214000005@10.85.127.166:php;51d367f4af94b85214000005-myns.rhcloud.com)
# return the combined exit code of the sync operation (non-zero means something failed)
function sync_gear_repos {
  local new_gear="${1}"
  shift
  local gears=("${@}")

  # Set the sync source base dir
  local SYNC_BASE_DIR=$OPENSHIFT_PRIMARY_CARTRIDGE_DIR

  export RSYNC_RSH="ssh"

  local rsynccmd="rsync -v --delete-after -az"
  if [ -f ${SYNC_BASE_DIR}/metadata/rsync.excludes ]; then
      rsynccmd="rsync --exclude-from=${SYNC_BASE_DIR}metadata/rsync.excludes --delete-after -az"
  fi

  # Defaults for sync-gears.  Override these in .env from the configure script
  if [ -z "$OPENSHIFT_SYNC_GEARS_DIRS" ]
  then
    local OPENSHIFT_SYNC_GEARS_DIRS=( "../app-root/repo" "node_modules" "virtenv" "../.m2" ".openshift" "deployments" "perl5lib" "phplib" )
  fi
  if [ -z "$OPENSHIFT_SYNC_GEARS_PRE" ]
  then
    local OPENSHIFT_SYNC_GEARS_PRE=('gear stop')

    # Only respect hot deployment for initialized gears
    if [ "$new_gear" = "false" ]; then
      if hot_deploy_marker_is_present; then
        echo "Application will not be stopped during gear sync to to presence of hot_deploy marker"
        OPENSHIFT_SYNC_GEARS_PRE=()
      fi
    fi
  fi
  if [ -z "$OPENSHIFT_SYNC_GEARS_POST" ]
  then
    local remote_deploy_cmd="gear remotedeploy"

    if [ "$new_gear" = "true" ]; then
      remote_deploy_cmd="${remote_deploy_cmd} --init"
    else
      # Only respect hot deployment for initialized gears
      if hot_deploy_marker_is_present; then
        remote_deploy_cmd="${remote_deploy_cmd} --hot-deploy=true"
      else
        remote_deploy_cmd="${remote_deploy_cmd} --hot-deploy=false"
      fi
    fi

    local OPENSHIFT_SYNC_GEARS_POST=("${remote_deploy_cmd}")
  fi

  local STDOUTS=()   # Set of outputs
  local EXITCODES=() # Set of exit codes

  for zinfo in "${gears[@]}"
  do
    local zarr=(${zinfo//;/ })
    local gear=${zarr[0]}
    local arr=(${gear//:/ })
    local sshcmd="ssh ${arr[0]}"
    echo "SSH_CMD: ${sshcmd}"
    local output=$(mktemp "/tmp/sync_gears.output.XXXXXXXXXXXXXXXX")
    STDOUTS+=("$output")
    local exitcode=$(mktemp "/tmp/sync_gears.exit.XXXXXXXXXXXXXXXX")
    EXITCODES+=("$exitcode")
    (
      (
        set -x -e
        echo "Syncing to gear: $gear @ " $(date)

        # Prepare remote gear for new content
        for rpccall in "${OPENSHIFT_SYNC_GEARS_PRE[@]}"
        do
          $sshcmd "$rpccall"
        done

        # Push content to remote gear
        for subd in "${OPENSHIFT_SYNC_GEARS_DIRS[@]}"
        do
          if [ -d "${SYNC_BASE_DIR}/${subd}" ]
          then
            $rsynccmd "${SYNC_BASE_DIR}/${subd}/" "${gear}/${subd}/"
          fi
        done

        # Post-sync calls & start
        for rpccall in "${OPENSHIFT_SYNC_GEARS_POST[@]}"
        do
          $sshcmd "$rpccall"
        done

      )
      echo $? > "$exitcode"
    ) >"$output" 2>&1 &
  done
  wait

  # Serialize outputs and exit codes for easier debugging
  local exc=0
  local slen=${#STDOUTS[@]}
  for (( i=0; i<${slen}; i++ ))
  do
    cat "${STDOUTS[$i]}"
    local pexc=$(cat "${EXITCODES[$i]}")
    echo "Exit code: $pexc"
    if [ "$pexc" != "0" ]; then
      exc=128   # TODO: instead? exc=$(($exc | $pexc))
    fi
    rm -f "${STDOUTS[$i]}" "${EXITCODES[$i]}"
  done

  return $exc
}
