<repo_name>jescholl/belt<file_sep>/share/toolbelt/tools/.hashicorp
#!/bin/bash

#set -x
name="$(basename -s .sh "$0")"

latest_version() {
  curl -Ls "https://checkpoint-api.hashicorp.com/v1/check/$name" | jq -r .current_version
}

remote_versions() {
  echo "Not supported" >&2
}

_url() {
  echo "https://releases.hashicorp.com/${name}/${version}/${name}_${version}_${os_type}_${os_arch}.zip"
}

install() {
  version=$1
  os_type=$2
  os_arch=$3
  install_path=$4

  tmpdir="$(mktemp -d)"
  if (cd "$tmpdir" && curl -Lsfo "${name}.zip" "$(_url)" && unzip "${name}.zip" && mv "$name" "$install_path"); then
    rm -rf "$tmpdir"
    chmod +x "$install_path"
  else
    rm -rf "$tmpdir"
    cat <<EOT
Unable to download
URL: $(_url)
path: $install_path
EOT
    exit 1
  fi
}

#_belt_completion_kubectl() {
#  case "$1" in
#    bash)
#      #source <(kubectl completion bash)
#      ;;
#    zsh)
#      _kubectl_lazy_completion() {
#      #  source <(kubectl completion zsh)
#        unset -f _kubectl_lazy_completion
#      }
#      compdef _kubectl_lazy_completion kubectl
#      ;;
#    *)
#      echo "shell not supported"
#      exit 5
#  esac
#}

# TODO: Fix completion, I believe this is the closest
#if [ -n "$ZSH_VERSION" ]; then
#  _kubectl_lazy_completion() {
#    source <(kubectl completion zsh)
#    unset -f _kubectl_lazy_completion
#  }
#  compdef _kubectl_lazy_completion kubectl
#elif [ -n "$BASH_VERSION" ]; then
#  source <(kubectl completion bash)
#fi

action=$1; shift
"${action}" "$@"
<file_sep>/bin/.belt_stub
#!/usr/bin/env bash

name="$(basename "$0")"
belt.sh exec "$name" "$@"
<file_sep>/README.md
# ARCHIVED
This project is no longer being maintained, check out [asdf](https://asdf-vm.com/) as an alternative.

# Belt
A single version manager for all your tools with one simple interface

### Instalation

1. Clone the repo somewhere (e.g. ~/belt)
```
git clone https://github.com/jescholl/belt.git ~/belt
```
2. Initialize belt in your .bashrc or .zshrc
```
source <(~/belt/belt.sh init)
```

### Usage

Simply use your favorite tools the way you normally would, they will be downloaded if necessary before use

#### Managing versions

There are multiple different ways to manage versions, and if no version is set it will simply use the latest version found locally, and download the latest remote version if none exists locally

* Environment variables
```
› export BELT_TERRAFORM_VERSION=1.0.10
› terraform version
Terraform v1.0.10
on darwin_amd64
```

* Version files
```
› cat .consul-version
1.10.3

› consul version
Consul v1.10.3
Revision c976ffd2d
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)
```

* Default versions
```
› belt use kubectl 1.22.2
› kubectl version
Client Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.2", GitCommit:"<KEY>", GitTreeState:"clean", BuildDate:"2021-09-15T21:38:50Z", GoVersion:"go1.16.8", Compiler:"gc", Platform:"darwin/amd64"}
```

#### List installed tools and versions

```
› belt versions
consul:
  * 1.10.3 (Set in /Users/jscholl/.consul-version)
kubectl:
  * 1.22.2 (Set in /Users/jscholl/.belt/.kubectl-version)
    1.10.3
    1.10.2
    1.10.1
    1.10.0
nomad:
  * 1.1.6 (No version set, using latest installed)
packer:
  * 1.7.7 (No version set, using latest installed)
terraform:
  * 1.0.10 (Set with $BELT_TERRAFORM_VERSION)
    1.0.9
vault:
  * 1.8.4 (No version set, using latest installed)
```


### Coming soon

* Autocomplete
* Better error messages
<file_sep>/test/belt_test.sh
. ./test/helper.sh


function test_environment_pollution()
{
  assertNull "belt is polluting the environment" "$SOURCE"
}
function test_belt_module()
{
  assertTrue "BELT_MODULE_DIR does not exist" "[ -d $BELT_MODULE_DIR ]"

}

# FIXME: I need to test (somewhere, not necessarily here) that it works from other directories
# or that the path is fully qualified and exists

SHUNIT_PARENT=$0 . $SHUNIT2
<file_sep>/bin/belt.sh
#!/usr/bin/env bash

#set -x

tool_version=""
tool_path=""
version_reason=""

belt_help() {
  cat <<EOT
Usage: belt <command> [<args>]

Some useful commands are:
  exec      <tool> <version> [<args>]
  install   <tool> <version>
  init
  use       <tool> <version>
  uninstall <tool> [version]
  upgrade   <tool>
  which     <tool>
  versions  [tool]
EOT
}

belt_init() {
  BELT_ROOT=$(cd -- "${0%/*}/../" && pwd -P)
  cat <<EOT
export BELT_ROOT="$BELT_ROOT"
export BELT_DIR="\${BELT_DIR:-$HOME/.belt}"
export PATH="\${BELT_ROOT}/bin:\$PATH"
belt() {
  local action="\$1"
  #if [ "\$action" == "use" ]; then
  #  local tool_name="\$2"
  #  local version="\$3"
  #  if [[ -n "\$tool_name" && -n "\$version" ]]; then
  #  fi
  #  export BELT_"\$(echo "\$tool_name" | tr '[:lower:]' '[:upper:]')"_VERSION="\$version"
  #else
    belt.sh "\$@"
  #fi
}
EOT
}

belt_use() {
  local tool_name="$1"
  local version="$2"
  if [[ "${#@}" -ne 2 || -z "$tool_name" || -z "$version" ]]; then
    error "Usage: belt use <tool> <version>"
  fi
  echo "$version" > "${BELT_DIR}/.${tool_name}-version"
}


log() {
  [ "$BELT_LOG" == "true" ] && echo "$@" >&2
}

error() {
  log "$@"
  exit 1
}

BELT_MODULE_DIR="$BELT_ROOT/share/toolbelt/tools" # FIXME: this is a dumb var name

belt_which() {
  tool_name=$1

  [ -n "$tool_name" ] || error "BELT WHICH: Must passs tool name"
  local tool_version version_reason # set by tool_version
  tool_version "$tool_name"

  log "BELT WHICH: $tool_name: ${BELT_DIR}/${tool_name}/${tool_version}"

  local tool_path="${BELT_DIR}/${tool_name}/${tool_version}"
  if [[ -f "$tool_path" || "$fetch_remote_version" == "true" ]]; then
    echo "$tool_path"
  else
    return 1
  fi
}

belt_exec() {
  tool_name=$1; shift
  #tool_path="${tool_path:-$(fetch_remote_version=true belt_which "$tool_name")}"

  log "BELT EXEC: $tool_name $*"

  if ! tool_path="$(belt_which "$tool_name")"; then
  #if ! [ -f "$tool_path" ]; then
    log "BELT_EXEC: Installing $tool_name"
    belt_install "$tool_name"
    tool_path="$(belt_which "$tool_name")"
  fi

  log "$tool_path" "$@"
  "$tool_path" "$@"
}

belt_install() {
  local tool_name=$1 tool_path
  fetch_remote_version=true tool_version "$tool_name"
  tool_path="$(fetch_remote_version=true belt_which "$tool_name")"
  #log "path = $(fetch_remote_version=true belt_which "$tool_name")"
  collect_system_info

  [ -d "${tool_path%/*}" ] || mkdir -p "${tool_path%/*}"
  log belt_module "$tool_name" install "$tool_version" "$os_type" "$os_arch" "$tool_path"
  if ! belt_module "$tool_name" install "$tool_version" "$os_type" "$os_arch" "$tool_path"; then
    exit "$?"
  fi
}

belt_module() {
  module=$1; shift
  "${BELT_MODULE_DIR}/${module}.sh" "$@"
  return "$?"
}

collect_system_info() {
  os_type=${OS_TYPE:-$(uname | tr '[:upper:]' '[:lower:]')}
  os_arch=${OS_ARCH:-$(uname -m)}
  os_arch=${os_arch/x86_64/amd64}
  os_arch=${os_arch/i[123456789]86/i386}

  if [[ -z "$os_type" || -z "$os_arch" ]]; then
    cat <<EOT
Unable to detect OS type and architecture
Please set the OS_TYPE and OS_ARCH environment variables
Example:
  export OS_TYPE=darwin # or linux, sunos, openbsd, freebsd
  export OS_ARCH=amd64 # or i386, arm
EOT
    return 1
  fi
}

belt_versions() {
  local only_tool="$1"

  for tool in "$BELT_MODULE_DIR/"*; do
    local tool_name current_version
    tool_name="$(basename -s .sh "$tool")"

    if [[ -n "$only_tool" && "${tool_name}" != "${only_tool}" ]]; then
      continue
    fi
    echo "$tool_name:"

    local tool_version version_reason # set by tool_version
    tool_version "$tool_name"
    #current_version="$(tool_version "$tool_name")"

    current_version="$tool_version"
    for version in $(tool_versions "$tool_name"); do
      if [[ "$version" == "$current_version" ]]; then
        echo "  * $version ($version_reason)"
      else
        echo "    $version"
      fi
    done
  done
}

tool_version() {
  local tool_name=$1
  local version reason version_var

  fetch_remote_version=${fetch_remote_version:-false}

  # check for env var
  version_var="\$BELT_$(echo "$tool_name" | tr '[:lower:]' '[:upper:]')_VERSION"
  version="$(eval echo "$version_var")"
  reason="Set with $version_var"
  #version="$(eval echo "\$BELT_$(echo "$tool_name" | tr '[:lower:]' '[:upper:]')_VERSION")"

  # check for .$tool_name-version
  if [ -z "$version" ]; then
    dir="$PWD"

    until [[ -z "$dir" || -f "${dir}/.${tool_name}-version" ]]; do
      dir=${dir%/*}
    done
    version="$(cat "${dir}/.${tool_name}-version" 2> /dev/null)"
    reason="Set in ${dir}/.${tool_name}-version"
  fi

  # check default version
  if [ -z "$version" ]; then
    version="${version:-$(cat "${BELT_DIR}/.${tool_name}-version" 2> /dev/null)}"
    reason="Set in ${BELT_DIR}/.${tool_name}-version"
  fi

  # check for the latest installed version
  if [ -z "$version" ]; then
    version="$(tool_versions "$tool_name" | head -n 1)"
    reason="No version set, using latest installed"
  fi

  # check for latest available
  if [[ -z "$version" && "$fetch_remote_version" == "true" ]]; then
    version="$(belt_module "$tool_name" latest_version )"
    reason="No version set, downloading latest"
  fi

  tool_version="$version"
  version_reason="$reason"
}

tool_versions() {
  local tool_name=$1
  local tool_dir="${BELT_DIR}/${tool_name}"

  if [[ -d "${tool_dir}" && -n "$(ls -A "${tool_dir}")" ]]; then
    versions=("${tool_dir}"/*)
    echo "${versions[@]##*/}" | tr ' ' '\n' | sort -rV
  fi
}

action=$1; shift
"belt_$action" "$@"
<file_sep>/share/toolbelt/tools/kubectl.sh
#!/bin/bash

latest_version() {
  curl -Ls https://storage.googleapis.com/kubernetes-release/release/stable.txt | sed 's/v\(.*\)/\1/'
}

remote_versions() {
  echo "Not supported" >&2
}


_url() {
  [ "${version:0:1}" == "v" ] || version="v${version}"

  echo "https://storage.googleapis.com/kubernetes-release/release/${version}/bin/${os_type}/${os_arch}/kubectl"
}

install() {
  version=$1
  os_type=$2
  os_arch=$3
  install_path=$4

  echo curl -Lsfo "$install_path" "$(_url)"
  if curl -Lsfo "$install_path" "$(_url)"; then
    chmod +x "$install_path"
  else
    cat <<EOT
Unable to download
URL: $(_url)
path: $install_path
EOT
    exit 1
  fi
}

#_belt_completion_kubectl() {
#  case "$1" in
#    bash)
#      #source <(kubectl completion bash)
#      ;;
#    zsh)
#      _kubectl_lazy_completion() {
#      #  source <(kubectl completion zsh)
#        unset -f _kubectl_lazy_completion
#      }
#      compdef _kubectl_lazy_completion kubectl
#      ;;
#    *)
#      echo "shell not supported"
#      exit 5
#  esac
#}

# TODO: Fix completion, I believe this is the closest
#if [ -n "$ZSH_VERSION" ]; then
#  _kubectl_lazy_completion() {
#    source <(kubectl completion zsh)
#    unset -f _kubectl_lazy_completion
#  }
#  compdef _kubectl_lazy_completion kubectl
#elif [ -n "$BASH_VERSION" ]; then
#  source <(kubectl completion bash)
#fi

action=$1; shift
"${action}" "$@"
<file_sep>/test/helper.sh
[[ -n "$ZSH_VERSION" ]] && setopt shwordsplit

SHUNIT2=${SHUNIT2:-$(command -v shunit2)}

export PREFIX="$PWD/test"
export HOME="$PREFIX/home"

. ./share/toolbelt/belt.sh

setUp() { return; }
tearDown() { return; }
oneTimeTearDown() { return; }
<file_sep>/test/kubectl_test.sh
. ./test/helper.sh

function _kubectl_version_parse()
{
  key=$1
  awk -F"[,:]" "{for(i=1;i<=NF;i++){if(\$i~/$key/){print \$(i+1)}}}" | tr -d '"'
}


function test_kubectl_is_a_function()
{
  local kubectl_path="$(command -v kubectl)"

  assertEquals "kubectl is not a function" "kubectl" "$kubectl_path"
}

function test_kubectl_environment()
{
  local BELT_KUBECTL_VERSION="v1.10.3"
  local client_version=$(kubectl version 2> /dev/null | _kubectl_version_parse 'GitVersion' )
  assertEquals "version doesn't match ENV" "v1.10.3" "$client_version"

  local BELT_KUBECTL_VERSION="v1.10.5"
  local client_version=$(kubectl version 2> /dev/null | _kubectl_version_parse 'GitVersion' )
  assertEquals "version doesn't match ENV" "v1.10.5" "$client_version"
}

function test_latest()
{
  local latest_version=$(belt latest kubectl)
  assertNotEquals "'latest' is empty" "$latest_version" ""
}

function test_url()
{
  local url
  url=$(belt module kubectl url darwin amd64 "v1.10.5")
  assertTrue "Unable to download: $url" "curl -Ifs $url"

  url=$(belt module kubectl url darwin i386)
  assertTrue "Unable to download: $url" "curl -Ifs $url"

  url=$(belt module kubectl url linux amd64)
  assertTrue "Unable to download: $url" "curl -Ifs $url"

  url=$(belt module kubectl url linux i386)
  assertTrue "Unable to download: $url" "curl -Ifs $url"
}

SHUNIT_PARENT=$0 . $SHUNIT2
