#!/bin/bash

# This script holds library functions for setting up the Docker container build environment

# os::build::environment::create creates a docker container with the default variables.
# arguments are passed directly to the container, OS_BUILD_ENV_GOLANG, OS_BUILD_ENV_IMAGE,
# and OS_RELEASE_DOCKER_ARGS can be used to customize the container. The docker socket
# is mounted by default and the output of the command is the container id.
function os::build::environment::create() {
  set -o errexit
  local release_image="${OS_BUILD_ENV_IMAGE}"
  local additional_context="${OS_BUILD_ENV_DOCKER_ARGS:-}"
  if [[ "${OS_BUILD_ENV_USE_DOCKER:-y}" == "y" ]]; then
    additional_context+="--privileged -v /var/run/docker.sock:/var/run/docker.sock"

    if [[ "${OS_BUILD_ENV_LOCAL_DOCKER:-n}" == "y" ]]; then
      # if OS_BUILD_ENV_LOCAL_DOCKER==y, add the local OS_ROOT as the bind mount to the working dir
      # and set the running user to the current user
      local workingdir
      workingdir=$( os::build::environment::release::workingdir )
      additional_context+=" -v ${OS_ROOT}:${workingdir} -u $(id -u)"
    elif [[ -n "${OS_BUILD_ENV_REUSE_VOLUME:-}" ]]; then
      # if OS_BUILD_ENV_REUSE_VOLUME is set, create a docker volume to store the working output so
      # successive iterations can reuse shared code.
      local workingdir
      workingdir=$( os::build::environment::release::workingdir )
      name="$( echo "${OS_BUILD_ENV_REUSE_VOLUME}" | tr '[:upper:]' '[:lower:]' )"
      docker volume create --name "${name}" > /dev/null
      additional_context+=" -v ${name}:${workingdir}"
    fi
  fi

  local args
  if [[ $# -eq 0 ]]; then
    args=( "echo" "docker create ${additional_context} ${release_image}" )
  else
    args=( "$@" )
  fi

  # Create a new container to from the release environment
  docker create ${additional_context} "${release_image}" "${args[@]}"
}
readonly -f os::build::environment::create

# os::build::environment::release::workingdir calculates the working directory for the current
# release image.
function os::build::environment::release::workingdir() {
  set -o errexit
  # get working directory
  local container
  container="$(docker create "${release_image}")"
  local workingdir
  workingdir="$(docker inspect -f '{{ index . "Config" "WorkingDir" }}' "${container}")"
  docker rm "${container}" > /dev/null
  echo "${workingdir}"
}
readonly -f os::build::environment::release::workingdir

# os::build::environment::cleanup stops and removes the container named in the argument
# (unless OS_BUILD_ENV_LEAVE_CONTAINER is set, in which case it will only stop the container).
function os::build::environment::cleanup() {
  local container=$1
  docker stop --time=0 "${container}" > /dev/null || true
  if [[ -z "${OS_BUILD_ENV_LEAVE_CONTAINER:-}" ]]; then
    docker rm "${container}" > /dev/null
  fi
}
readonly -f os::build::environment::cleanup

# os::build::environment::start starts the container provided as the first argument
# using whatever content exists in the container already.
function os::build::environment::start() {
  local container=$1

  docker start "${container}" > /dev/null
  docker logs -f "${container}"

  local exitcode
  exitcode="$( docker inspect --type container -f '{{ .State.ExitCode }}' "${container}" )"

  # extract content from the image
  if [[ -n "${OS_BUILD_ENV_PRESERVE-}" ]]; then
    local workingdir
    workingdir="$(docker inspect -f '{{ index . "Config" "WorkingDir" }}' "${container}")"
    local oldIFS="${IFS}"
    IFS=:
    for path in ${OS_BUILD_ENV_PRESERVE}; do
      local parent=.
      if [[ "${path}" != "." ]]; then
        parent="$( dirname "${path}" )"
        mkdir -p "${parent}"
      fi
      docker cp "${container}:${workingdir}/${path}" "${parent}"
    done
    IFS="${oldIFS}"
  fi
  return "${exitcode}"
}
readonly -f os::build::environment::start

# os::build::environment::withsource starts the container provided as the first argument
# after copying in the contents of the current Git repository at HEAD (or, if specified,
# the ref specified in the second argument).
function os::build::environment::withsource() {
  local container=$1
  local commit=${2:-HEAD}

  if [[ -n "${OS_BUILD_ENV_LOCAL_DOCKER-}" ]]; then
    # running locally, no change necessary
    os::build::get_version_vars
    os::build::save_version_vars "${OS_ROOT}/os-version-defs"
  else
    local workingdir
    workingdir="$(docker inspect -f '{{ index . "Config" "WorkingDir" }}' "${container}")"
    if [[ -n "${OS_BUILD_ENV_REUSE_VOLUME-}" ]]; then
      local excluded=()
      local oldIFS="${IFS}"
      IFS=:
      for exclude in ${OS_BUILD_ENV_EXCLUDE:-_output}; do
        excluded+=("--exclude=${exclude}")
      done
      IFS="${oldIFS}"
      if which rsync &>/dev/null; then
        local name
        name="$( echo "${OS_BUILD_ENV_REUSE_VOLUME}" | tr '[:upper:]' '[:lower:]' )"
        if ! rsync -a --blocking-io "${excluded[@]}" --omit-dir-times --numeric-ids -e "docker run --rm -i -v \"${name}:${workingdir}\" --entrypoint=/bin/bash \"${OS_BUILD_ENV_IMAGE}\" -c '\$@'" . remote:"${workingdir}"; then
          # fall back to a tar if rsync is not in container
          tar -cf - "${excluded[@]}" . | docker cp - "${container}:${workingdir}"
        fi
      else
        tar -cf - "${excluded[@]}" . | docker cp - "${container}:${workingdir}"
      fi
    else
      # Generate version definitions. Tree state is clean because we are pulling from git directly.
      OS_GIT_TREE_STATE=clean os::build::get_version_vars
      os::build::save_version_vars "/tmp/os-version-defs"

      tar -cf - -C /tmp/ os-version-defs | docker cp - "${container}:${workingdir}"
      git archive --format=tar "${commit}" | docker cp - "${container}:${workingdir}"
    fi
  fi

  os::build::environment::start "${container}"
}
readonly -f os::build::environment::withsource

# os::build::environment::run launches the container with the provided arguments and
# the current commit (defaults to HEAD). The container is automatically cleaned up.
function os::build::environment::run() {
  local commit="${OS_GIT_COMMIT:-HEAD}"
  local volume="${OS_BUILD_ENV_REUSE_VOLUME:-}"
  if [[ -z "${volume}" ]]; then
    volume="origin-build-$( git rev-parse "${commit}" )"
  fi

  local container
  container="$( OS_BUILD_ENV_REUSE_VOLUME=${volume} os::build::environment::create "$@" )"
  trap "os::build::environment::cleanup ${container}" EXIT

  os::build::environment::withsource "${container}" "${commit}"
}
readonly -f os::build::environment::run