function refresh_docker_images {
   # Arguments:
   #   $1 - Path to top level Consul source
   #   $2 - Which make target to invoke (optional)
   #
   # Return:
   #   0 - success
   #   * - failure

   if ! test -d "$1"
   then
      err "ERROR: '$1' is not a directory. refresh_docker_images must be called with the path to the top level source as the first argument'"
      return 1
   fi

   local sdir="$1"
   local targets="$2"

   test -n "${targets}" || targets="docker-images"

   make -C "${sdir}" ${targets}
   return $?
}

function build_ui {
   # Arguments:
   #   $1 - Path to the top level Consul source
   #   $2 - The docker image to run the build within (optional)
   #   $3 - Version override
   #
   # Returns:
   #   0 - success
   #   * - error
   #
   # Notes:
   #   Use the GIT_COMMIT environment variable to pass off to the build
   #   Use the GIT_COMMIT_YEAR environment variable to pass off to the build

   if ! test -d "$1"
   then
      err "ERROR: '$1' is not a directory. build_ui must be called with the path to the top level source as the first argument'"
      return 1
   fi

   local image_name=${UI_BUILD_CONTAINER_DEFAULT}
   if test -n "$2"
   then
      image_name="$2"
   fi

   local sdir="$1"
   local ui_dir="${1}/ui-v2"

   # parse the version
   version=$(parse_version "${sdir}")

   if test -n "$3"
   then
      version="$3"
   fi

   local commit_hash="${GIT_COMMIT}"
   if test -z "${commit_hash}"
   then
      commit_hash=$(git rev-parse --short HEAD)
   fi
   
   local commit_year="${GIT_COMMIT_YEAR}"
   if test -z "${commit_year}"
   then
      commit_year=$(git show -s --format=%cd --date=format:%Y HEAD)
   fi
   
   local logo_type="${CONSUL_BINARY_TYPE}"
   if test "$logo_type" != "oss"
   then
     logo_type="enterprise"
   fi

   # make sure we run within the ui dir
   pushd ${ui_dir} > /dev/null

   status "Creating the UI Build Container with image: ${image_name} and version '${version}'"
   local container_id=$(docker create -it -e "CONSUL_GIT_SHA=${commit_hash}" -e "CONSUL_COPYRIGHT_YEAR=${commit_year}" -e "CONSUL_VERSION=${version}" -e "CONSUL_BINARY_TYPE=${CONSUL_BINARY_TYPE}" ${image_name})
   local ret=$?
   if test $ret -eq 0
   then
      status "Copying the source from '${ui_dir}' to /consul-src within the container"
      (
         tar -c $(ls -A | grep -v "^(node_modules\|dist\|tmp)") | docker cp - ${container_id}:/consul-src &&
         status "Running build in container" && docker start -i ${container_id} &&
         rm -rf ${1}/ui-v2/dist &&
         status "Copying back artifacts" && docker cp ${container_id}:/consul-src/dist ${1}/ui-v2/dist
      )
      ret=$?
      docker rm ${container_id} > /dev/null
   fi

   # Check the version is baked in correctly
   if test ${ret} -eq 0
   then
      local ui_vers=$(ui_version "${1}/ui-v2/dist/index.html")
      if test "${version}" != "${ui_vers}"
      then
         err "ERROR: UI version mismatch. Expecting: '${version}' found '${ui_vers}'"
         ret=1
      fi
   fi

   # Check the logo is baked in correctly
   if test ${ret} -eq 0
   then
     local ui_logo_type=$(ui_logo_type "${1}/ui-v2/dist/index.html")
     if test "${logo_type}" != "${ui_logo_type}"
     then
       err "ERROR: UI logo type mismatch. Expecting: '${logo_type}' found '${ui_logo_type}'"
       ret=1
     fi
   fi

   # Copy UI over ready to be packaged into the binary
   if test ${ret} -eq 0
   then
      rm -rf ${1}/pkg/web_ui
      mkdir -p ${1}/pkg
      cp -r ${1}/ui-v2/dist ${1}/pkg/web_ui
   fi

   popd > /dev/null
   return $ret
}

function build_assetfs {
   # Arguments:
   #   $1 - Path to the top level Consul source
   #   $2 - The docker image to run the build within (optional)
   #
   # Returns:
   #   0 - success
   #   * - error
   #
   # Note:
   #   The GIT_COMMIT, GIT_DIRTY and GIT_DESCRIBE environment variables will be used if present

   if ! test -d "$1"
   then
      err "ERROR: '$1' is not a directory. build_assetfs must be called with the path to the top level source as the first argument'"
      return 1
   fi

   local sdir="$1"
   local image_name=${GO_BUILD_CONTAINER_DEFAULT}
   if test -n "$2"
   then
      image_name="$2"
   fi

   pushd ${sdir} > /dev/null
   status "Creating the Go Build Container with image: ${image_name}"
   local container_id=$(docker create -it -e GIT_COMMIT=${GIT_COMMIT} -e GIT_DIRTY=${GIT_DIRTY} -e GIT_DESCRIBE=${GIT_DESCRIBE} ${image_name} make static-assets ASSETFS_PATH=bindata_assetfs.go)
   local ret=$?
   if test $ret -eq 0
   then
      status "Copying the sources from '${sdir}/(pkg/web_ui|GNUmakefile)' to /consul/pkg"
      (
         tar -c pkg/web_ui GNUmakefile | docker cp - ${container_id}:/consul &&
         status "Running build in container" && docker start -i ${container_id} &&
         status "Copying back artifacts" && docker cp ${container_id}:/consul/bindata_assetfs.go ${sdir}/agent/bindata_assetfs.go
      )
      ret=$?
      docker rm ${container_id} > /dev/null
   fi
   popd >/dev/null
   return $ret
}

function build_consul_post {
   # Arguments
   #   $1 - Path to the top level Consul source
   #   $2 - Subdirectory under pkg/bin (Optional)
   #
   # Returns:
   #   0 - success
   #   * - error
   #
   # Notes:
   #   pkg/bin is where to place binary packages
   #   pkg.bin.new is where the just built binaries are located
   #   bin is where to place the local systems versions

   if ! test -d "$1"
   then
      err "ERROR: '$1' is not a directory. build_consul_post must be called with the path to the top level source as the first argument'"
      return 1
   fi

   local sdir="$1"

   local extra_dir_name="$2"
   local extra_dir=""

   if test -n "${extra_dir_name}"
   then
      extra_dir="${extra_dir_name}/"
   fi

   pushd "${sdir}" > /dev/null

   # recreate the pkg dir
   rm -r pkg/bin/${extra_dir}* 2> /dev/null
   mkdir -p pkg/bin/${extra_dir} 2> /dev/null

   # move all files in pkg.new into pkg
   cp -r pkg.bin.new/${extra_dir}* pkg/bin/${extra_dir}
   rm -r pkg.bin.new

   DEV_PLATFORM="./pkg/bin/${extra_dir}$(go env GOOS)_$(go env GOARCH)"
   for F in $(find ${DEV_PLATFORM} -mindepth 1 -maxdepth 1 -type f 2>/dev/null)
   do
      # recreate the bin dir
      rm -r bin/* 2> /dev/null
      mkdir -p bin 2> /dev/null

      cp ${F} bin/
      cp ${F} ${MAIN_GOPATH}/bin
   done

   popd > /dev/null

   return 0
}

function build_consul {
   # Arguments:
   #   $1 - Path to the top level Consul source
   #   $2 - Subdirectory to put binaries in under pkg/bin (optional - must specify if needing to specify the docker image)
   #   $3 - The docker image to run the build within (optional)
   #
   # Returns:
   #   0 - success
   #   * - error
   #
   # Note:
   #   The GOLDFLAGS and GOTAGS environment variables will be used if set
   #   If the CONSUL_DEV environment var is truthy only the local platform/architecture is built.
   #   If the XC_OS or the XC_ARCH environment vars are present then only those platforms/architectures
   #   will be built. Otherwise all supported platform/architectures are built

   if ! test -d "$1"
   then
      err "ERROR: '$1' is not a directory. build_consul must be called with the path to the top level source as the first argument'"
      return 1
   fi

   local sdir="$1"
   local extra_dir_name="$2"
   local extra_dir=""
   local image_name=${GO_BUILD_CONTAINER_DEFAULT}
   if test -n "$3"
   then
      image_name="$3"
   fi

   pushd ${sdir} > /dev/null
   if is_set "${CONSUL_DEV}"
   then
      if test -z "${XC_OS}"
      then
         XC_OS=$(go env GOOS)
      fi

      if test -z "${XC_ARCH}"
      then
         XC_ARCH=$(go env GOARCH)
      fi
   fi
   XC_OS=${XC_OS:-"solaris darwin freebsd linux windows"}
   XC_ARCH=${XC_ARCH:-"386 amd64 arm arm64"}

   if test -n "${extra_dir_name}"
   then
      extra_dir="${extra_dir_name}/"
   fi

   # figure out if the compiler supports modules
   local use_modules=0
   if go help modules >/dev/null 2>&1
   then
      use_modules=1
   elif test -n "${GO111MODULE}"
   then
      use_modules=1
   fi

   local volume_mount=
   if is_set "${use_modules}"
   then
      status "Ensuring Go modules are up to date"
      # ensure our go module cache is correct
      go_mod_assert || return 1
      # setup to bind mount our hosts module cache into the container
      volume_mount="--mount=type=bind,source=${MAIN_GOPATH}/pkg/mod,target=/go/pkg/mod"
   fi

   status "Creating the Go Build Container with image: ${image_name}"
   local container_id=$(docker create -it \
      ${volume_mount} \
      -e CGO_ENABLED=0 \
      -e GOLDFLAGS="${GOLDFLAGS}" \
      -e GOTAGS="${GOTAGS}" \
      ${image_name} \
      ./build-support/scripts/build-local.sh -o "${XC_OS}" -a "${XC_ARCH}")
   ret=$?

   if test $ret -eq 0
   then
      status "Copying the source from '${sdir}' to /consul"
      (
         tar -c $(ls | grep -v "^(ui\|ui-v2\|website\|bin\|pkg\|.git)") | docker cp - ${container_id}:/consul &&
         status "Running build in container" &&
         docker start -i ${container_id} &&
         status "Copying back artifacts" &&
         docker cp ${container_id}:/consul/pkg/bin pkg.bin.new
      )
      ret=$?
      docker rm ${container_id} > /dev/null

      if test $ret -eq 0
      then
         build_consul_post "${sdir}" "${extra_dir_name}"
         ret=$?
      else
         rm -r pkg.bin.new 2> /dev/null
      fi
   fi
   popd > /dev/null
   return $ret
}

function build_consul_local {
   # Arguments:
   #   $1 - Path to the top level Consul source
   #   $2 - Space separated string of OSes to build. If empty will use env vars for determination.
   #   $3 - Space separated string of architectures to build. If empty will use env vars for determination.
   #   $4 - Subdirectory to put binaries in under pkg/bin (optional)
   #
   # Returns:
   #   0 - success
   #   * - error
   #
   # Note:
   #   The GOLDFLAGS and GOTAGS environment variables will be used if set
   #   If the CONSUL_DEV environment var is truthy only the local platform/architecture is built.
   #   If the XC_OS or the XC_ARCH environment vars are present then only those platforms/architectures
   #   will be built. Otherwise all supported platform/architectures are built
   #   The GOXPARALLEL environment variable is used if set

   if ! test -d "$1"
   then
      err "ERROR: '$1' is not a directory. build_consul must be called with the path to the top level source as the first argument'"
      return 1
   fi

   local sdir="$1"
   local build_os="$2"
   local build_arch="$3"
   local extra_dir_name="$4"
   local extra_dir=""

   if test -n "${extra_dir_name}"
   then
      extra_dir="${extra_dir_name}/"
   fi

   pushd ${sdir} > /dev/null
   if is_set "${CONSUL_DEV}"
   then
      if test -z "${XC_OS}"
      then
         XC_OS=$(go env GOOS)
      fi

      if test -z "${XC_ARCH}"
      then
         XC_ARCH=$(go env GOARCH)
      fi
   fi
   XC_OS=${XC_OS:-"solaris darwin freebsd linux windows"}
   XC_ARCH=${XC_ARCH:-"386 amd64 arm arm64"}

   if test -z "${build_os}"
   then
      build_os="${XC_OS}"
   fi

   if test -z "${build_arch}"
   then
      build_arch="${XC_ARCH}"
   fi

   status_stage "==> Building Consul - OSes: ${build_os}, Architectures: ${build_arch}"
   mkdir pkg.bin.new 2> /dev/null

   status "Building sequentially with go install"
   for os in ${build_os}
   do
      for arch in ${build_arch}
      do
         outdir="pkg.bin.new/${extra_dir}${os}_${arch}"
         osarch="${os}/${arch}"

         case "${os}" in
            "darwin" )
               # Do not build ARM binaries for macOS
               if test "${arch}" == "arm" -o "${arch}" == "arm64"
               then
                  continue
               fi
               ;;
            "windows" )
               # Do not build ARM binaries for Windows
               if test "${arch}" == "arm" -o "${arch}" == "arm64"
               then
                  continue
               fi
               ;;
            "freebsd" )
               # Do not build ARM binaries for FreeBSD
               if test "${arch}" == "arm" -o "${arch}" == "arm64"
               then
                  continue
               fi
               ;;
            "solaris" )
               # Only build amd64 for Solaris
               if test "${arch}" != "amd64"
               then
                  continue
               fi            
               ;;
            "linux" )
               # build all the binaries for Linux
               ;;
            *)
               continue
            ;;
         esac         

         echo "--->   ${osarch}"

         mkdir -p "${outdir}"
         GOBIN_EXTRA=""
         if test "${os}" != "$(go env GOHOSTOS)" -o "${arch}" != "$(go env GOHOSTARCH)"
         then
            GOBIN_EXTRA="${os}_${arch}/"
         fi
         binname="consul"
         if [ $os == "windows" ];then
               binname="consul.exe"
         fi
         debug_run env CGO_ENABLED=0 GOOS=${os} GOARCH=${arch} go install -ldflags "${GOLDFLAGS}" -tags "${GOTAGS}" && cp "${MAIN_GOPATH}/bin/${GOBIN_EXTRA}${binname}" "${outdir}/${binname}"
         if test $? -ne 0
         then
            err "ERROR: Failed to build Consul for ${osarch}"
            rm -r pkg.bin.new
            return 1
         fi
      done
   done

   build_consul_post "${sdir}" "${extra_dir_name}"
   if test $? -ne 0
   then
      err "ERROR: Failed postprocessing Consul binaries"
      return 1
   fi
   return 0
}