#!/bin/sh set -eu ARGS="$*" SCRIPT_VERSION="(unreleased version)" if [ "$SCRIPT_VERSION" = "(unreleased version)" ]; then IMAGE_VERSION=latest else IMAGE_VERSION="$SCRIPT_VERSION" fi IMAGE_VERSION=${VERSION:-$IMAGE_VERSION} DOCKERHUB_USER=${DOCKERHUB_USER:-weaveworks} SCOPE_IMAGE_NAME="$DOCKERHUB_USER/scope" SCOPE_IMAGE="$SCOPE_IMAGE_NAME:$IMAGE_VERSION" # Careful: it's easy to operate on (e.g. stop) the wrong scope instance # when SCOPE{_APP,}_CONTAINER_NAME values differ between runs. Handle # with care. SCOPE_CONTAINER_NAME="${SCOPE_CONTAINER_NAME:-weavescope}" SCOPE_APP_CONTAINER_NAME="${SCOPE_APP_CONTAINER_NAME:-weavescope-app}" IP_REGEXP="[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}" IP_ADDR_CMD="find /sys/class/net -type l | xargs -n1 basename | grep -vE 'docker|veth|lo' | \ xargs -n1 ip addr show | grep inet | awk '{ print \$2 }' | grep -oE '$IP_REGEXP'" LISTENING_IP_ADDR_CMD="for I in \$( $IP_ADDR_CMD ); do if curl -m 1 -s \${I}:4040 > /dev/null ; then echo \${I}; fi; done" WEAVESCOPE_DOCKER_ARGS=${WEAVESCOPE_DOCKER_ARGS:-} # When docker daemon is running with User Namespace enabled, this tool will run into errors: # "Privileged mode is incompatible with user namespaces" for `docker run --privileged` # "Cannot share the host's network namespace when user namespaces are enabled" for `docker run --net=host` # To avoid above errors, use `--userns=host` option to let container use host User Namespace. # This option(saved in $USERNS_HOST) will be inserted ONLY IF docker support `--userns` option. USERNS_HOST="" docker run --help | grep -q -- --userns && USERNS_HOST="--userns=host" usage() { name=$(basename "$0") cat >&2 <<-EOF Usage: $name launch {OPTIONS} {PEERS} - Launch Scope $name stop - Stop Scope $name command - Print the docker command used to start Scope $name help - Print usage info $name version - Print version info PEERS are of the form HOST[:PORT] HOST may be an ip or hostname. PORT defaults to 4040. Launch options: EOF docker run --rm --entrypoint=/home/weave/scope "$SCOPE_IMAGE" -h >&2 } usage_and_die() { usage exit 1 } [ $# -gt 0 ] || usage_and_die COMMAND=$1 shift 1 check_docker_access() { # Extract socket path DOCKER_SOCK_FILE="" if [ -z "${DOCKER_HOST+x}" ]; then DOCKER_SOCK_FILE="/var/run/docker.sock" else WITHOUT_PREFIX="${DOCKER_HOST#unix://}" if [ "$WITHOUT_PREFIX" != "$DOCKER_HOST" ]; then DOCKER_SOCK_FILE="$WITHOUT_PREFIX" fi fi # shellcheck disable=SC2166 if [ \( -n "$DOCKER_SOCK_FILE" \) -a \( ! -w "$DOCKER_SOCK_FILE" \) ]; then echo "ERROR: cannot write to docker socket: $DOCKER_SOCK_FILE" >&2 echo "change socket permissions or try using sudo" >&2 exit 1 fi } # - The image embeds the weave script & Docker 1.13.1 client (mimicking a 1.10 client) # - Weave needs 1.10.0 now (image pulling changes) MIN_DOCKER_VERSION=1.10.0 check_docker_version() { if ! DOCKER_VERSION=$(docker -v | sed -n 's%^Docker version \([0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\).*$%\1%p') \ || [ -z "$DOCKER_VERSION" ]; then echo "ERROR: Unable to parse docker version" >&2 exit 1 fi DOCKER_VERSION_MAJOR=$(echo "$DOCKER_VERSION" | cut -d. -f 1) DOCKER_VERSION_MINOR=$(echo "$DOCKER_VERSION" | cut -d. -f 2) DOCKER_VERSION_PATCH=$(echo "$DOCKER_VERSION" | cut -d. -f 3) MIN_DOCKER_VERSION_MAJOR=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 1) MIN_DOCKER_VERSION_MINOR=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 2) MIN_DOCKER_VERSION_PATCH=$(echo "$MIN_DOCKER_VERSION" | cut -d. -f 3) # shellcheck disable=SC2166 if [ \( "$DOCKER_VERSION_MAJOR" -lt "$MIN_DOCKER_VERSION_MAJOR" \) -o \ \( "$DOCKER_VERSION_MAJOR" -eq "$MIN_DOCKER_VERSION_MAJOR" -a \ \( "$DOCKER_VERSION_MINOR" -lt "$MIN_DOCKER_VERSION_MINOR" -o \ \( "$DOCKER_VERSION_MINOR" -eq "$MIN_DOCKER_VERSION_MINOR" -a \ \( "$DOCKER_VERSION_PATCH" -lt "$MIN_DOCKER_VERSION_PATCH" \) \) \) \) ]; then echo "ERROR: scope requires Docker version $MIN_DOCKER_VERSION or later; you are running $DOCKER_VERSION" >&2 exit 1 fi } check_probe_only() { echo "${ARGS}" | grep -q -E -e "--no-app|--service-token|--probe-only" } check_listen_address_arg() { echo "${ARGS}" | grep -q -E -e "--app\\.http\\.address" } check_docker_for_mac() { [ "$(uname)" = "Darwin" ] \ && [ -S /var/run/docker.sock ] \ && [ ! "${DOCKER_HOST+x}" = x ] \ && [ "${HOME+x}" = x ] \ && [ -d "${HOME}/Library/Containers/com.docker.docker/Data" ] } # Check that a container named $1 with image $2 is not running check_not_running() { case $(docker inspect --format='{{.State.Running}} {{.Config.Image}}' "$1" 2>/dev/null) in "true $2") echo "$1 is already running." >&2 exit 1 ;; "true $2:"*) echo "$1 is already running." >&2 exit 1 ;; "false $2") docker rm "$1" >/dev/null ;; "false $2:"*) docker rm "$1" >/dev/null ;; true*) echo "Found another running container named '$1'. Aborting." >&2 exit 1 ;; false*) echo "Found another container named '$1'. Aborting." >&2 exit 1 ;; esac } check_plugins_dir() { # If plugins dir exists for Docker containers then we will mount it # (the context for Docker might be different to that for this script, e.g. when using Docker for Mac) if docker run $USERNS_HOST --rm --entrypoint=/bin/sh \ -v /var/run:/var/run \ "$SCOPE_IMAGE" -c "test -d /var/run/scope/plugins"; then PLUGINS_DIR_EXISTS=true fi } docker_args() { echo --privileged $USERNS_HOST --net=host --pid=host \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /sys/kernel/debug:/sys/kernel/debug \ -e CHECKPOINT_DISABLE # shellcheck disable=SC2039 [ -n "${PLUGINS_DIR_EXISTS:-}" ] && echo -v /var/run/scope/plugins:/var/run/scope/plugins } launch_command() { # shellcheck disable=SC2046,SC2086 echo docker run -d --name="$SCOPE_CONTAINER_NAME" $(docker_args) \ $WEAVESCOPE_DOCKER_ARGS "$SCOPE_IMAGE" --probe.docker=true } launch_docker4mac_app_command() { # shellcheck disable=SC2086 echo docker run -d --name="$SCOPE_APP_CONTAINER_NAME" \ -e CHECKPOINT_DISABLE \ -p 0.0.0.0:4040:4040 \ $WEAVESCOPE_DOCKER_ARGS "$SCOPE_IMAGE" --no-probe } launch() { check_not_running "$SCOPE_CONTAINER_NAME" "$SCOPE_IMAGE_NAME" docker rm -f "$SCOPE_CONTAINER_NAME" >/dev/null 2>&1 || true $(launch_command) "$@" echo "Scope probe started" } print_app_endpoints() { HOST_SUFFIX="" if [ -n "${DOCKER_HOST+x}" ]; then DOCKER_HOSTNAME=$(run_in_scope_container hostname) HOST_SUFFIX=" of host $DOCKER_HOSTNAME" fi echo "Weave Scope is listening at the following URL(s)${HOST_SUFFIX}:" >&2 for ip in "$@"; do echo " * http://$ip:4040/" >&2 done } dry_run() { # Do a dry run of scope in the foreground, so it can parse args etc # avoiding the entrypoint script in the process. # shellcheck disable=SC2046 docker run --rm --entrypoint=/home/weave/scope $(docker_args) "$SCOPE_IMAGE" --dry-run "$@" } run_in_scope_container() { docker run --rm $USERNS_HOST --net=host --entrypoint /bin/sh "$SCOPE_IMAGE" -c "$1" } # Wait for the scope app to start listening on localhost:4040 wait_for_http() { for seconds in $(seq 5); do if run_in_scope_container "curl -m 1 -s localhost:4040" >/dev/null; then break fi sleep 1 done if [ "$seconds" -eq 5 ]; then echo "The Scope App is not responding. Consult the container logs for further details." exit 1 fi } check_docker_access check_docker_version case "$COMMAND" in command) # Most systems should have printf, but the %q specifier isn't mandated by posix # and can't be guaranteed. Since this is mainly a cosmetic output and the alternative # is not making any attempt to do escaping at all, we might as well try. # shellcheck disable=SC2039 quoted=$(printf '%q ' "$@" 2>/dev/null || true) # printf %q behaves oddly with zero args (it acts as though it received one empty arg) # so we ignore that case. if [ -z "$quoted" ] || [ $# -eq 0 ]; then quoted="$*" fi echo "$(launch_command) $quoted" ;; version) docker run --rm --entrypoint=/home/weave/scope "$SCOPE_IMAGE" --mode=version ;; -h | help | -help | --help) usage ;; launch) dry_run "$@" check_plugins_dir if check_docker_for_mac; then if check_probe_only; then launch "$@" exit fi # Docker for Mac (as of beta9) does not ship vmnet driver and # thereby only access container ports via a tunnel, preventing # access to host ports of the VM. # - https://github.com/weaveworks/scope/issues/1411 # - https://forums.docker.com/t/ports-in-host-network-namespace-are-not-accessible/10789 if check_listen_address_arg; then echo "--app.http.address argument not supported on Docker for Mac" >&2 exit 1 fi check_not_running "$SCOPE_APP_CONTAINER_NAME" "$SCOPE_IMAGE_NAME" check_not_running "$SCOPE_CONTAINER_NAME" "$SCOPE_IMAGE_NAME" docker rm -f "$SCOPE_APP_CONTAINER_NAME" >/dev/null 2>&1 || true CONTAINER=$($(launch_docker4mac_app_command) "$@") echo "Scope probe started" app_ip=$(docker inspect -f '{{.NetworkSettings.IPAddress}}' "${CONTAINER}") docker rm -f "$SCOPE_CONTAINER_NAME" >/dev/null 2>&1 || true # shellcheck disable=SC2091 CONTAINER=$($(launch_command) --no-app "$@" "${app_ip}:4040") print_app_endpoints "localhost" exit fi launch "$@" if ! check_probe_only; then if check_listen_address_arg; then echo "Weave Scope is listening at the address specified with --app.http.address" >&2 else wait_for_http IP_ADDRS=$(run_in_scope_container "$LISTENING_IP_ADDR_CMD") # shellcheck disable=SC2086 print_app_endpoints $IP_ADDRS fi fi ;; stop) [ $# -eq 0 ] || usage_and_die if docker inspect "$SCOPE_CONTAINER_NAME" >/dev/null 2>&1; then docker stop "$SCOPE_CONTAINER_NAME" >/dev/null fi if check_docker_for_mac; then if docker inspect "$SCOPE_APP_CONTAINER_NAME" >/dev/null 2>&1; then docker stop "$SCOPE_APP_CONTAINER_NAME" >/dev/null fi fi ;; *) echo "Unknown scope command '$COMMAND'" >&2 usage_and_die ;; esac