<commit_before>#!/bin/bash
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#

#
# Copyright (c) 2015, Joyent, Inc.
#

#
# Setup your environment for `docker` to use with SmartDataCenter.
#
# The basic steps are:
#
# 1. Select the data center (i.e. the CloudAPI URL).
# 2. Select the account (login) to use.
# 3. Ensure the account has an SSH key to use.
# 4. Generate a client certificate from your SSH key and save that where
#    `docker` can use it: "~/.sdc/docker/$account/".
#

if [[ -n "$TRACE" ]]; then
    export PS4='[\D{%FT%TZ}] ${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
    set -o xtrace
fi
set -o errexit
set -o pipefail

# ---- globals

NAME=$(basename $0)
VERSION=1.0.0

CERT_BASE_DIR=$HOME/.sdc/docker

CURL_OPTS=" -H user-agent:sdc-docker-setup/$VERSION"

PROFILE_NAME_RE='^[a-z][a-z0-9\._-]+$'



# ---- support functions

function fatal
{
    echo "" >&2
    echo "* * *" >&2
    printf "$NAME: fatal error: $*\n" >&2
    exit 1
}

function warn
{
    echo "$NAME: warn: $*" >&2
}

function info
{
    if [[ $optQuiet == "true" ]]; then
        return
    fi
    echo "$*"
}

function envInfo
{
    if [[ -n "$envFile" ]]; then
	echo "$*" >>$envFile
    fi
    echo "    $*"
}

function debug
{
    #echo "$NAME: debug: $@" >&2
    true
}


function usage
{
    echo "Usage:"
    echo "  sdc-docker-setup [SDC-CLOUDAPI-OR-REGION] [ACCOUNT] [SSH-PRIVATE-KEY-PATH]"
    echo ""
    echo "Options:"
    echo "  -h      Print this help and exit."
    echo "  -V      Print version and exit."
    echo "  -q      Quiet output. Only print out environment setup commands."
    echo "  -f      Force set up without checks (check that the given login and"
    echo "          ssh key exist in the SDC CloudAPI, check that the Docker"
    echo "          hostname responds, etc)."
    echo "  -k      Disable SSH certificate verification (e.g. if using CoaL"
    echo "          for development)."
    echo "  -s      Include SDC_* environment variables for setting up SDC CLI."
    echo "          Otherwise, only the 'docker' env vars are emitted."
    echo "  -p PROFILE"
    echo "          The profile name for this Docker host and account."
    echo "          Profile info is stored under '~/.sdc/docker/$profile/'."
    echo "          It defaults to the ACCOUNT, it must match '${PROFILE_NAME_RE}'."
    # TODO: examples
}

function dockerInfo
{
    local dockerUrl response
    dockerUrl=$1

    local curlOpts
    if [[ $optInsecure == "true" ]]; then
        curlOpts=" -k"
    fi
    curl $CURL_OPTS -sSf $curlOpts --connect-timeout 10 \
        --url $dockerUrl/v1.16/info
}



# Return ssh fingerprint in the form "he:xh:ex:he:xh:..."
# OpenSSH_6.8 changes the "-l" flag output.
function sshGetMD5Fingerprint() {
    local sshPubKeyPath=$1
    local s
    s=$(ssh-keygen -E md5 -l -f "$sshPubKeyPath" 2> /dev/null)
    if [[ $? -eq  0 ]]; then
        echo "$s" | awk '{print $2}' | tr -d '\n' | cut -d: -f2-;
    else
        # OpenSSH version < 6.8
        ssh-keygen -l -f "$sshPubKeyPath" | awk '{print $2}' | tr -d '\n';
    fi
}


function cloudapiVerifyAccount() {
    local cloudapiUrl account sshPrivKeyPath sshKeyId now signature response
    cloudapiUrl=$1
    account=$2
    sshPrivKeyPath=$3
    sshKeyId=$4

    now=$(date -u "+%a, %d %h %Y %H:%M:%S GMT")
    signature=$(echo ${now} | tr -d '\n' | openssl dgst -sha256 -sign $sshPrivKeyPath | openssl enc -e -a | tr -d '\n')

    local curlOpts
    if [[ $coal == "true" || $optInsecure == "true" ]]; then
        curlOpts=" -k"
    fi

    local response status
    response=$(curl $CURL_OPTS $curlOpts -isS \
        -H "Accept:application/json" -H "api-version:*" -H "Date: ${now}" \
        -H "Authorization: Signature keyId=\"/$account/keys/$sshKeyId\",algorithm=\"rsa-sha256\" ${signature}" \
        --url $cloudapiUrl/--ping)
    status=$(echo "$response" | head -1 | awk '{print $2}')
    case "$status" in
        401)
            if [[ -n "$portalUrl" ]]; then
                fatal "invalid credentials" \
                    "\nVisit <$portalUrl> to create the '$account' account" \
                    "\nand/or add your SSH public key ($sshPubKeyPath)"
            elif [[ "$coal" == "true" ]]; then
                fatal "invalid credentials" \
                    "\n    You must add create the '$account' account and/or add your SSH" \
                    "\n    public key ($sshPubKeyPath) to the" \
                    "\n    given SmartDataCenter."\
                    "\n" \
                    "\n    On CoaL you can do this via:" \
                    "\n        scp $sshPubKeyPath root@10.99.99.7:/var/tmp/id_rsa.pub" \
                    "\n        ssh root@10.99.99.7" \
                    "\n        sdc-useradm get $account >/dev/null 2>/dev/null || \\" \
                    "\n            echo '{\"login\":\"$account\",\"userpassword\":\"<PASSWORD>\",\"cn\":\"$account Test User\",\"email\":\"<EMAIL>\"}' | sdc-useradm create -A" \
                    "\n        sdc-useradm add-key $account /var/tmp/id_rsa.pub"
            else
                fatal "invalid credentials" \
                    "\n    You must add create the '$account' account and/or add your SSH" \
                    "\n    public key ($sshPubKeyPath) to the" \
                    "\n    given SmartDataCenter."
            fi
            ;;
        200)
            info "CloudAPI access verified."
            info ''
            ;;
        *)
            if [[ "$status" == "400" && "$coal" == "true" ]]; then
                fatal "'Bad Request' from CloudAPI. Possibly clock skew. Otherwise, check the CloudAPI log.\n\n$response"
            fi
            fatal "unexpected CloudAPI response:\n\n$response"
            ;;
    esac
}


function cloudapiGetDockerService() {
    local cloudapiUrl account sshPrivKeyPath sshKeyId now signature response
    cloudapiUrl=$1
    account=$2
    sshPrivKeyPath=$3
    sshKeyId=$4

    # TODO: share the 'cloudapi request' code
    now=$(date -u "+%a, %d %h %Y %H:%M:%S GMT")
    signature=$(echo ${now} | tr -d '\n' | openssl dgst -sha256 -sign $sshPrivKeyPath | openssl enc -e -a | tr -d '\n')

    local curlOpts
    if [[ $coal == "true" || $optInsecure == "true" ]]; then
        curlOpts=" -k"
    fi

    # TODO: a test on ListServices being a single line of JSON
    local response status dockerService
    response=$(curl $CURL_OPTS $curlOpts -isS \
        -H "Accept:application/json" -H "api-version:*" -H "Date: ${now}" \
        -H "Authorization: Signature keyId=\"/$account/keys/$sshKeyId\",algorithm=\"rsa-sha256\" ${signature}" \
        --url $cloudapiUrl/$account/services)
    status=$(echo "$response" | head -1 | awk '{print $2}')
    if [[ "$status" == "403" ]]; then
        # Forbidden (presumably from an invite-only DC).
        # Assuming the error response is all on the last line:
        #   {"code":"NotAuthorized","message":"Forbidden (This serv ..."}
        local errmsg
        errmsg=$(echo "$response" | tail -1 | sed -E 's/.*"message":"([^"]*)".*/\1/')
        fatal "cannot setup for this datacenter: $errmsg"
    elif [[ "$status" != "200" ]]; then
        warn "could not get Docker service endpoint from CloudAPI (status=$status)"
        return
    fi
    if [[ -z "$(echo "$response" | (grep '"docker"' || true))" ]]; then
        warn "could not get Docker service endpoint from CloudAPI (no docker service listed)"
        return
    fi
    dockerService=$(echo "$response" | tail -1 | sed -E 's/.*"docker":"([^"]*)".*/\1/')
    if [[ "$dockerService" != "$response" ]]; then
        echo $dockerService
    fi
}

function downloadCaCertificate()
{
    local dockerHttpsUrl="https://$1"
    local outFile=$2
    local curlOpts=""

    if [[ "$coal" == "true" || $optInsecure == "true" ]]; then
        curlOpts="-k"
    fi

    curl $CURL_OPTS $curlOpts --connect-timeout 10 \
        --url "$dockerHttpsUrl/ca.pem" -o $outFile 2>/dev/null
}

# Arguments:
#   $1 - the function that will handle the printing
#   $2 - the indentation string
function sdcEnvConfiguration()
{
    local indent=$2
    if [[ -n "$optSdcSetup" ]]; then
        $1 "${indent}export SDC_URL=$cloudapiUrl"
        $1 "${indent}export SDC_ACCOUNT=$account"
        if [[ -f $sshPubKeyPath ]]; then
            $1 "${indent}export SDC_KEY_ID=$(sshGetMD5Fingerprint $sshPubKeyPath)"
        else
            $1 "${indent}# Could not calculate KEY_ID: SSH public key '$sshPubKeyPath' does not exist"
            $1 "${indent}export SDC_KEY_ID='<fingerprint of SSH public key for $(basename $sshPrivKeyPath)>'"
        fi
        if [[ "$coal" == "true" || $optInsecure == "true" ]]; then
            $1 "${indent}export SDC_TESTING=1"
        fi
    fi
}



# ---- mainline

# This script currently requires Bash-isms, so guard for that.
if [ "$POSIXLY_CORRECT" = "y" ]; then
    fatal "This script requires Bash running in *non*-posix mode.
Please re-run with 'bash sdc-docker-setup.sh ...'."
fi

optQuiet=
optForce=
optInsecure=
optSdcSetup=
optProfileName=
while getopts "hVqfksp:" opt; do
    case "$opt" in
        h)
            usage
            exit 0
            ;;
        V)
            echo "$(basename $0) $VERSION"
            exit 0
            ;;
        q)
            optQuiet=true
            ;;
        f)
            optForce=true
            ;;
        k)
            optInsecure=true
            ;;
        s)
            optSdcSetup=true
            ;;
        p)
            if [[ -z $(echo "$OPTARG" | (egrep "$PROFILE_NAME_RE" || true)) ]]; then
                fatal "profile name, '$OPTARG', does not match '$PROFILE_NAME_RE'"
            fi
            optProfileName=$OPTARG
            ;;
        *)
            usage
            exit 1
            ;;
    esac
done
shift $((OPTIND - 1))


# Get the cloudapi URL. Default to the cloudapi for the current pre-release
# docker service. Eventually can default to the user's SDC_URL setting.
#
# Offer some shortcuts:
# - coal: Find the cloudapi in your local CoaL via ssh.
# - <string without dots>: Treat as a Joyent Cloud region name and use:
#       https://$dc.api.joyent.com
# - if given without 'https://' prefix: add that automatically
promptedUser=
cloudapiUrl=$1
if [[ -z "$cloudapiUrl" ]]; then
    defaultCloudapiUrl=https://us-east-1.api.joyent.com
    #info "Enter the SDC Docker hostname. Press enter for the default."
    printf "SDC CloudAPI URL [$defaultCloudapiUrl]: "
    read cloudapiUrl
    promptedUser=true
fi
if [[ -z "$cloudapiUrl" ]]; then
    portalUrl=https://my.joyent.com
    cloudapiUrl=$defaultCloudapiUrl
elif [[ "$cloudapiUrl" == "coal" ]]; then
    coal=true
    cloudapiUrl=https://$(ssh -o ConnectTimeout=5 root@10.99.99.7 "vmadm lookup -j alias=cloudapi0 | json -ae 'ext = this.nics.filter(function (nic) { return nic.nic_tag === \"external\"; })[0]; this.ip = ext ? ext.ip : this.nics[0].ip;' ip")
    if [[ -z "$cloudapiUrl" ]]; then
        fatal "could not find the cloudapi0 zone IP in CoaL"
    fi
elif [[ "${cloudapiUrl/./X}" == "$cloudapiUrl" ]]; then
    portalUrl=https://my.joyent.com
    cloudapiUrl=https://$cloudapiUrl.api.joyent.com
elif [[ "${cloudapiUrl:0:8}" != "https://" ]]; then
    cloudapiUrl=https://$cloudapiUrl
fi
debug "cloudapiUrl: $cloudapiUrl"


# Get the account to use.
account=$2
if [[ -z "$account" ]]; then
    defaultAccount=$SDC_ACCOUNT
    if [[ -z "$defaultAccount" ]]; then
        printf "SDC account: "
    else
        printf "SDC account [$defaultAccount]: "
    fi
    read account
    promptedUser=true
fi
if [[ -z "$account" && -n "$defaultAccount" ]]; then
    account=$defaultAccount
fi
debug "account: $account"
if [[ -z "$account" ]]; then
    fatal "no account (login name) was given"
fi


# Get SSH priv key path.
sshPrivKeyPath=$3
if [[ -z "$sshPrivKeyPath" ]]; then
    # TODO: Using SDC_KEY_ID and search ~/.ssh for a matching key.<commit_msg>DOCKER-618: check for invalid cloudapi URL<commit_after>#!/bin/bash
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#

#
# Copyright (c) 2015, Joyent, Inc.
#

#
# Setup your environment for `docker` to use with SmartDataCenter.
#
# The basic steps are:
#
# 1. Select the data center (i.e. the CloudAPI URL).
# 2. Select the account (login) to use.
# 3. Ensure the account has an SSH key to use.
# 4. Generate a client certificate from your SSH key and save that where
#    `docker` can use it: "~/.sdc/docker/$account/".
#

if [[ -n "$TRACE" ]]; then
    export PS4='[\D{%FT%TZ}] ${BASH_SOURCE}:${LINENO}: ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
    set -o xtrace
fi
set -o errexit
set -o pipefail

# ---- globals

NAME=$(basename $0)
VERSION=1.0.0

CERT_BASE_DIR=$HOME/.sdc/docker

CURL_OPTS=" -H user-agent:sdc-docker-setup/$VERSION"

PROFILE_NAME_RE='^[a-z][a-z0-9\._-]+$'



# ---- support functions

function fatal
{
    echo "" >&2
    echo "* * *" >&2
    printf "$NAME: fatal error: $*\n" >&2
    exit 1
}

function warn
{
    echo "$NAME: warn: $*" >&2
}

function info
{
    if [[ $optQuiet == "true" ]]; then
        return
    fi
    echo "$*"
}

function envInfo
{
    if [[ -n "$envFile" ]]; then
	echo "$*" >>$envFile
    fi
    echo "    $*"
}

function debug
{
    #echo "$NAME: debug: $@" >&2
    true
}


function usage
{
    echo "Usage:"
    echo "  sdc-docker-setup [SDC-CLOUDAPI-OR-REGION] [ACCOUNT] [SSH-PRIVATE-KEY-PATH]"
    echo ""
    echo "Options:"
    echo "  -h      Print this help and exit."
    echo "  -V      Print version and exit."
    echo "  -q      Quiet output. Only print out environment setup commands."
    echo "  -f      Force set up without checks (check that the given login and"
    echo "          ssh key exist in the SDC CloudAPI, check that the Docker"
    echo "          hostname responds, etc)."
    echo "  -k      Disable SSH certificate verification (e.g. if using CoaL"
    echo "          for development)."
    echo "  -s      Include SDC_* environment variables for setting up SDC CLI."
    echo "          Otherwise, only the 'docker' env vars are emitted."
    echo "  -p PROFILE"
    echo "          The profile name for this Docker host and account."
    echo "          Profile info is stored under '~/.sdc/docker/$profile/'."
    echo "          It defaults to the ACCOUNT, it must match '${PROFILE_NAME_RE}'."
    # TODO: examples
}

function dockerInfo
{
    local dockerUrl response
    dockerUrl=$1

    local curlOpts
    if [[ $optInsecure == "true" ]]; then
        curlOpts=" -k"
    fi
    curl $CURL_OPTS -sSf $curlOpts --connect-timeout 10 \
        --url $dockerUrl/v1.16/info
}



# Return ssh fingerprint in the form "he:xh:ex:he:xh:..."
# OpenSSH_6.8 changes the "-l" flag output.
function sshGetMD5Fingerprint() {
    local sshPubKeyPath=$1
    local s
    s=$(ssh-keygen -E md5 -l -f "$sshPubKeyPath" 2> /dev/null)
    if [[ $? -eq  0 ]]; then
        echo "$s" | awk '{print $2}' | tr -d '\n' | cut -d: -f2-;
    else
        # OpenSSH version < 6.8
        ssh-keygen -l -f "$sshPubKeyPath" | awk '{print $2}' | tr -d '\n';
    fi
}


function cloudapiVerifyAccount() {
    local cloudapiUrl account sshPrivKeyPath sshKeyId now signature response
    cloudapiUrl=$1
    account=$2
    sshPrivKeyPath=$3
    sshKeyId=$4

    now=$(date -u "+%a, %d %h %Y %H:%M:%S GMT")
    signature=$(echo ${now} | tr -d '\n' | openssl dgst -sha256 -sign $sshPrivKeyPath | openssl enc -e -a | tr -d '\n')

    local curlOpts
    if [[ $coal == "true" || $optInsecure == "true" ]]; then
        curlOpts=" -k"
    fi

    local response status
    response=$(curl $CURL_OPTS $curlOpts -isS \
        -H "Accept:application/json" -H "api-version:*" -H "Date: ${now}" \
        -H "Authorization: Signature keyId=\"/$account/keys/$sshKeyId\",algorithm=\"rsa-sha256\" ${signature}" \
        --url $cloudapiUrl/--ping)
    status=$(echo "$response" | head -1 | awk '{print $2}')
    case "$status" in
        401)
            if [[ -n "$portalUrl" ]]; then
                fatal "invalid credentials" \
                    "\nVisit <$portalUrl> to create the '$account' account" \
                    "\nand/or add your SSH public key ($sshPubKeyPath)"
            elif [[ "$coal" == "true" ]]; then
                fatal "invalid credentials" \
                    "\n    You must add create the '$account' account and/or add your SSH" \
                    "\n    public key ($sshPubKeyPath) to the" \
                    "\n    given SmartDataCenter."\
                    "\n" \
                    "\n    On CoaL you can do this via:" \
                    "\n        scp $sshPubKeyPath root@10.99.99.7:/var/tmp/id_rsa.pub" \
                    "\n        ssh root@10.99.99.7" \
                    "\n        sdc-useradm get $account >/dev/null 2>/dev/null || \\" \
                    "\n            echo '{\"login\":\"$account\",\"userpassword\":\"<PASSWORD>\",\"cn\":\"$account Test User\",\"email\":\"<EMAIL>\"}' | sdc-useradm create -A" \
                    "\n        sdc-useradm add-key $account /var/tmp/id_rsa.pub"
            else
                fatal "invalid credentials" \
                    "\n    You must add create the '$account' account and/or add your SSH" \
                    "\n    public key ($sshPubKeyPath) to the" \
                    "\n    given SmartDataCenter."
            fi
            ;;
        200)
            info "CloudAPI access verified."
            info ''
            ;;
        *)
            if [[ "$status" == "400" && "$coal" == "true" ]]; then
                fatal "'Bad Request' from CloudAPI. Possibly clock skew. Otherwise, check the CloudAPI log.\n\n$response"
            fi
            fatal "unexpected CloudAPI response:\n\n$response"
            ;;
    esac
}


function cloudapiGetDockerService() {
    local cloudapiUrl account sshPrivKeyPath sshKeyId now signature response
    cloudapiUrl=$1
    account=$2
    sshPrivKeyPath=$3
    sshKeyId=$4

    # TODO: share the 'cloudapi request' code
    now=$(date -u "+%a, %d %h %Y %H:%M:%S GMT")
    signature=$(echo ${now} | tr -d '\n' | openssl dgst -sha256 -sign $sshPrivKeyPath | openssl enc -e -a | tr -d '\n')

    local curlOpts
    if [[ $coal == "true" || $optInsecure == "true" ]]; then
        curlOpts=" -k"
    fi

    # TODO: a test on ListServices being a single line of JSON
    local response status dockerService
    response=$(curl $CURL_OPTS $curlOpts -isS \
        -H "Accept:application/json" -H "api-version:*" -H "Date: ${now}" \
        -H "Authorization: Signature keyId=\"/$account/keys/$sshKeyId\",algorithm=\"rsa-sha256\" ${signature}" \
        --url $cloudapiUrl/$account/services)
    status=$(echo "$response" | head -1 | awk '{print $2}')
    if [[ "$status" == "403" ]]; then
        # Forbidden (presumably from an invite-only DC).
        # Assuming the error response is all on the last line:
        #   {"code":"NotAuthorized","message":"Forbidden (This serv ..."}
        local errmsg
        errmsg=$(echo "$response" | tail -1 | sed -E 's/.*"message":"([^"]*)".*/\1/')
        fatal "cannot setup for this datacenter: $errmsg"
    elif [[ "$status" != "200" ]]; then
        warn "could not get Docker service endpoint from CloudAPI (status=$status)"
        return
    fi
    if [[ -z "$(echo "$response" | (grep '"docker"' || true))" ]]; then
        warn "could not get Docker service endpoint from CloudAPI (no docker service listed)"
        return
    fi
    dockerService=$(echo "$response" | tail -1 | sed -E 's/.*"docker":"([^"]*)".*/\1/')
    if [[ "$dockerService" != "$response" ]]; then
        echo $dockerService
    fi
}

function downloadCaCertificate()
{
    local dockerHttpsUrl="https://$1"
    local outFile=$2
    local curlOpts=""

    if [[ "$coal" == "true" || $optInsecure == "true" ]]; then
        curlOpts="-k"
    fi

    curl $CURL_OPTS $curlOpts --connect-timeout 10 \
        --url "$dockerHttpsUrl/ca.pem" -o $outFile 2>/dev/null
}

# Arguments:
#   $1 - the function that will handle the printing
#   $2 - the indentation string
function sdcEnvConfiguration()
{
    local indent=$2
    if [[ -n "$optSdcSetup" ]]; then
        $1 "${indent}export SDC_URL=$cloudapiUrl"
        $1 "${indent}export SDC_ACCOUNT=$account"
        if [[ -f $sshPubKeyPath ]]; then
            $1 "${indent}export SDC_KEY_ID=$(sshGetMD5Fingerprint $sshPubKeyPath)"
        else
            $1 "${indent}# Could not calculate KEY_ID: SSH public key '$sshPubKeyPath' does not exist"
            $1 "${indent}export SDC_KEY_ID='<fingerprint of SSH public key for $(basename $sshPrivKeyPath)>'"
        fi
        if [[ "$coal" == "true" || $optInsecure == "true" ]]; then
            $1 "${indent}export SDC_TESTING=1"
        fi
    fi
}



# ---- mainline

# This script currently requires Bash-isms, so guard for that.
if [ "$POSIXLY_CORRECT" = "y" ]; then
    fatal "This script requires Bash running in *non*-posix mode.
Please re-run with 'bash sdc-docker-setup.sh ...'."
fi

optQuiet=
optForce=
optInsecure=
optSdcSetup=
optProfileName=
while getopts "hVqfksp:" opt; do
    case "$opt" in
        h)
            usage
            exit 0
            ;;
        V)
            echo "$(basename $0) $VERSION"
            exit 0
            ;;
        q)
            optQuiet=true
            ;;
        f)
            optForce=true
            ;;
        k)
            optInsecure=true
            ;;
        s)
            optSdcSetup=true
            ;;
        p)
            if [[ -z $(echo "$OPTARG" | (egrep "$PROFILE_NAME_RE" || true)) ]]; then
                fatal "profile name, '$OPTARG', does not match '$PROFILE_NAME_RE'"
            fi
            optProfileName=$OPTARG
            ;;
        *)
            usage
            exit 1
            ;;
    esac
done
shift $((OPTIND - 1))

# Ping the URL passed as the first positional parameter and outputs one
# of three possible strings:
# - "available" if the ping endpoint responded with an OK status to the ping
# request
# - "maintenance" if the ping endpoint responded with a 503 status code to
# the ping request.
# - "unavailable" if the ping endpoint responded with any other response
function pingCloudAPIUrl()
{
    local cloudApiUrl=$1
    local response status
    response=$(curl -sSi $cloudApiUrl/--ping)
    status=$(echo "$response" | head -1 | awk '{print $2}')
    case "$status" in
        200)
            echo "available"
            ;;
        503)
            echo "maintenance"
            ;;
        *)
            echo "unavailable"
            ;;
    esac
}

# Get the cloudapi URL. Default to the cloudapi for the current pre-release
# docker service. Eventually can default to the user's SDC_URL setting.
#
# Offer some shortcuts:
# - coal: Find the cloudapi in your local CoaL via ssh.
# - <string without dots>: Treat as a Joyent Cloud region name and use:
#       https://$dc.api.joyent.com
# - if given without 'https://' prefix: add that automatically
promptedUser=

# While the URL passed on the command line is not a valid cloudapi URL,
# prompt for a new one.
cloudapiUrl=$1
while true; do
    if [[ -z "$cloudapiUrl" ]]; then
        defaultCloudapiUrl=https://us-east-1.api.joyent.com
        #info "Enter the SDC Docker hostname. Press enter for the default."
        printf "SDC CloudAPI URL [$defaultCloudapiUrl]: "
        read cloudapiUrl
        promptedUser=true
    fi

    if [[ -z "$cloudapiUrl" ]]; then
        portalUrl=https://my.joyent.com
        cloudapiUrl=$defaultCloudapiUrl
    elif [[ "$cloudapiUrl" == "coal" ]]; then
        coal=true
        cloudapiUrl=https://$(ssh -o ConnectTimeout=5 root@10.99.99.7 "vmadm lookup -j alias=cloudapi0 | json -ae 'ext = this.nics.filter(function (nic) { return nic.nic_tag === \"external\"; })[0]; this.ip = ext ? ext.ip : this.nics[0].ip;' ip")
        if [[ -z "$cloudapiUrl" ]]; then
            fatal "could not find the cloudapi0 zone IP in CoaL"
        fi
    elif [[ "${cloudapiUrl/./X}" == "$cloudapiUrl" ]]; then
        portalUrl=https://my.joyent.com
        cloudapiUrl=https://$cloudapiUrl.api.joyent.com
    elif [[ "${cloudapiUrl:0:8}" != "https://" ]]; then
        cloudapiUrl=https://$cloudapiUrl
    fi
    debug "cloudapiUrl: $cloudapiUrl"

    cloudApiUrlStatus=$(pingCloudAPIUrl "$cloudapiUrl")
    if [[ "$cloudApiUrlStatus" == "maintenance" ]]; then
        printf "\"$cloudapiUrl\" is currently in maintenance, please try another SDC CloudAPI URL\n"
    elif [[ "$cloudApiUrlStatus" == "unavailable" ]]; then
        printf "Cannot ping \"$cloudapiUrl\", are you sure it is a valid SDC CloudAPI URL?\n"
    elif [[ "$cloudApiUrlStatus" == "available" ]]; then
        break
    else
        fatal "There was an error when checking $cloudapiUrl's status\n"
    fi

    # Even if cloudapiUrl was set on the command line, reset it so that
    # we prompt for a new one because the one passed on the command line
    # was invalid.
    cloudapiUrl=
done


# Get the account to use.
account=$2
if [[ -z "$account" ]]; then
    defaultAccount=$SDC_ACCOUNT
    if [[ -z "$defaultAccount" ]]; then
        printf "SDC account: "
    else
        printf "SDC account [$defaultAccount]: "
    fi
    read account
    promptedUser=true
fi
if [[ -z "$account" && -n "$defaultAccount" ]]; then
    account=$defaultAccount
fi
debug "account: $account"
if [[ -z "$account" ]]; then
    fatal "no account (login name) was given"
fi


# Get SSH priv key path.
sshPrivKeyPath=$3
if [[ -z "$sshPrivKeyPath" ]]; then
    # TODO: Using SDC_KEY_ID and search ~/.ssh for a matching key.
{'fraction_non_alphanumeric': 0.14439663526766677, 'fraction_numerical': 0.011590495018800636, 'mean_word_length': 2.1778763242177877, 'pattern_counts': {'":': 16, '<': 15, '<?xml version=': 0, '>': 35, 'https://': 20, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 6, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6783422', 'n_tokens_mistral': 8611, 'n_tokens_neox': 8152, 'n_words': 2584}