#!/bin/bash
# Source: https://github.com/lukaszlach/clip/
# Docker Client Plugin Manager (CLIP)
#
# (c) 2019 Łukasz Lach
#   llach@llach.pl
#   https://lach.dev
if [[ "$1" == "docker-cli-plugin-metadata" ]]; then
cat <<CUT
{
     "SchemaVersion": "0.1.0",
     "Vendor": "Łukasz Lach",
     "Version": "v19.06.0",
     "ShortDescription": "Docker Client Plugins Manager"
}
CUT
exit
fi

USER_LOCAL_DIR="${XDG_CONFIG_HOME:-$HOME/.docker}/cli-plugins"

#
# docker clip build
#
cmd_build() {
    while getopts ":t:c:" opt; do
      case ${opt} in
        t ) PLUGIN_TAG=$OPTARG ;;
        c ) PLUGIN_COMMAND=$OPTARG ;;
        \? )
          echo "Invalid option: $OPTARG" 1>&2
          break
          ;;
        : )
          echo "Invalid option: $OPTARG requires an argument" 1>&2
          break
          ;;
      esac
    done
    shift $((OPTIND -1))
    PLUGIN_REPOSITORY=$(echo "$PLUGIN_TAG" | cut -d ':' -f 1)
    PLUGIN_CONTEXT="$1"
    if [ ! -e "$PLUGIN_CONTEXT/docker-$PLUGIN_COMMAND" ]; then
        echo "Error: docker-$PLUGIN_COMMAND file does not exist in the build context"
        exit 1
    elif [ ! -x "$PLUGIN_CONTEXT/docker-$PLUGIN_COMMAND" ]; then
        echo "Error: docker-$PLUGIN_COMMAND file is not executable"
        exit 1
    elif [ ! -e "$PLUGIN_CONTEXT/docker-$PLUGIN_COMMAND.json" ]; then
        echo "Error: docker-$PLUGIN_COMMAND.json file does not exist in the build context"
        exit 1
    fi
    BUILD_FILE=$(mktemp)
    cat > "$BUILD_FILE" <<EOF
FROM scratch
LABEL com.docker.clip="1" \
      com.docker.clip.tag="${PLUGIN_TAG}" \
      com.docker.clip.repository="${PLUGIN_REPOSITORY}" \
      com.docker.clip.command="${PLUGIN_COMMAND}" \
      com.docker.clip.build_date="$(date +'%F %T')"
COPY . .
EOF
    echo "Building $PLUGIN_TAG client plugin"
    docker build -q --force-rm --no-cache -t "$PLUGIN_TAG" -f "$BUILD_FILE" "$PLUGIN_CONTEXT"
    rm -f "$BUILD_FILE"
    echo "Successfully built $PLUGIN_TAG client plugin"
}

#
# docker clip push
#
cmd_push() {
    PLUGIN_TAG="$1"
    echo "Pushing $PLUGIN_TAG client plugin"
    docker push "$PLUGIN_TAG"
}

#
# docker clip add
#
cmd_add() {
    PLUGIN_TAG="$1"
    PLUGIN_REPOSITORY=$(echo "$PLUGIN_TAG" | cut -d ':' -f 1)
    IMAGE_TAG=$(echo "$PLUGIN_TAG:" | cut -d ':' -f 2)
    if [ -z "$IMAGE_TAG" ]; then PLUGIN_TAG="$PLUGIN_TAG:latest"; fi
    echo "Installing client plugin from $PLUGIN_TAG"
    REMOVE_PLUGIN_IMAGE=0
    if ! docker images --format '{{.Repository}}:{{.Tag}}' | grep -e "^$PLUGIN_TAG$" &>/dev/null; then
        docker pull "$PLUGIN_TAG"
        REMOVE_PLUGIN_IMAGE=1
    fi
    PLUGIN_FLAG=$(docker image inspect --format '{{index .Config.Labels "com.docker.clip"}}' "$PLUGIN_TAG")
    PLUGIN_COMMAND=$(docker image inspect --format '{{index .Config.Labels "com.docker.clip.command"}}' "$PLUGIN_TAG")
    PLUGIN_BIN="$USER_LOCAL_DIR/docker-$PLUGIN_COMMAND"
    if [ "$PLUGIN_FLAG" != "1" ]; then
        echo "Error: Image $PLUGIN_TAG is not a valid plugin image"
        exit 1
    fi
    if [ -e "$PLUGIN_BIN" ]; then
        echo "Error: $PLUGIN_TAG exports '$PLUGIN_COMMAND' client command that already exists"
        exit 1
    fi
    PLUGIN_LOCAL_DIR="$USER_LOCAL_DIR/.command/$PLUGIN_COMMAND"
    rm -rf "$PLUGIN_LOCAL_DIR"
    mkdir -p "$PLUGIN_LOCAL_DIR"
    IMAGE_DIR=$(mktemp -d)
    docker save "$PLUGIN_TAG" | tar -C "$IMAGE_DIR" -x
    find "$IMAGE_DIR" -name 'layer.tar' -exec tar -C "$PLUGIN_LOCAL_DIR" -xf {} \;
    rm -rf "$IMAGE_DIR"
    echo "$PLUGIN_TAG" > "$PLUGIN_LOCAL_DIR/.plugin-image"
    if [[ "$REMOVE_PLUGIN_IMAGE" == "1" ]]; then
        docker rmi "$PLUGIN_TAG" >/dev/null
    fi
    # Install command
    cat > "$USER_LOCAL_DIR/docker-$PLUGIN_COMMAND" <<EOF
#!/usr/bin/env bash
exec docker clip run "$PLUGIN_COMMAND" "\$@"
EOF
    chmod +x "$USER_LOCAL_DIR/docker-$PLUGIN_COMMAND"
    echo "Successfully installed $PLUGIN_TAG client plugin"
    echo "New client command available: 'docker $PLUGIN_COMMAND'"
}

#
# docker clip update
#
cmd_update() {
    PLUGIN_COMMAND="$1"
    PLUGIN_LOCAL_DIR="$USER_LOCAL_DIR/.command/$PLUGIN_COMMAND"
    PLUGIN_TAG=$(cat "$PLUGIN_LOCAL_DIR/.plugin-image")
    echo "Updating the $PLUGIN_COMMAND client command from $PLUGIN_TAG"
    cmd_rm "$PLUGIN_COMMAND"
    docker rmi "$PLUGIN_TAG" >/dev/null || true
    cmd_install "$PLUGIN_TAG"
    echo "Successfully updated the $PLUGIN_COMMAND client command"
}

#
# docker clip rm
#
cmd_rm() {
    PLUGIN_COMMAND="$1"
    if [ -z "$PLUGIN_COMMAND" ]; then
        echo "Error: Command name was not specified"
        exit 1
    fi
    PLUGIN_LOCAL_DIR="$USER_LOCAL_DIR/.command/$PLUGIN_COMMAND"
    if [ -d "$PLUGIN_LOCAL_DIR" ]; then
        rm -rf "$PLUGIN_LOCAL_DIR"
        rm -f "$USER_LOCAL_DIR/docker-$PLUGIN_COMMAND"
        echo "Successfully removed '$PLUGIN_COMMAND' client command"
    else
        if [ -e "$USER_LOCAL_DIR/docker-$PLUGIN_COMMAND" ]; then
            echo "Error: Client command '$PLUGIN_COMMAND' is not maintained by docker-clip"
            exit 1
        fi
        echo "Error: Client command '$PLUGIN_COMMAND' is not installed"
        exit 1
    fi
}

#
# docker clip ls
#
cmd_ls() {
    printf "%-20s %s\n" COMMAND IMAGE
    while read PLUGIN_IMAGE_FILE; do
        PLUGIN_TAG=$(cat "$PLUGIN_IMAGE_FILE")
        PLUGIN_COMMAND=$(basename $(dirname "$PLUGIN_IMAGE_FILE"))
        printf "%-20s %s\n" "$PLUGIN_COMMAND" "$PLUGIN_TAG"
    done < <(find "$USER_LOCAL_DIR/.command" -name .plugin-image)
}

#
# docker clip search
#
cmd_search() {
    PATTERN="$1"
    printf "COMMAND         IMAGE                           DESCRIPTION\n"
    curl -sSfL https://raw.githubusercontent.com/lukaszlach/clip/master/clips/catalog | \
        grep --color=always -ie "$PATTERN"
    exit $?
}

#
# docker clip run
# Used internally only
#
cmd_run() {
    PLUGIN_COMMAND="$1"
    PLUGIN_LOCAL_DIR="$USER_LOCAL_DIR/.command/$PLUGIN_COMMAND"
    shift
    if [[ "$1" == "docker-cli-plugin-metadata" ]]; then
        cat "$PLUGIN_LOCAL_DIR/docker-$PLUGIN_COMMAND.json"
        exit 0
    fi
    exec "$PLUGIN_LOCAL_DIR/docker-$PLUGIN_COMMAND" "$@"
    exit 0
}

usage() {
cat <<EOF

Usage: docker clip COMMAND
  
Commands:
  add       Install a new plugin by an image name
  build     Build a new plugin
  ls        List installed plugins
  push      Push local plugin to a Docker registry
  rm        Remove an installed plugin
  search    Search plugins from the remote catalog
  update    Reinstall a plugin to its latest version

EOF
}

shift
COMMAND="$1"
shift

if [ -z "$COMMAND" ] || [ "$COMMAND" == "-h" ] || [ "$COMMAND" == "--help" ]; then
    usage
    exit 0
fi

set -e
case "$COMMAND" in
    build | push | add | update | rm | ls | search | run)
      eval "cmd_${COMMAND}" "$@"
    ;;
esac

exit 0
