UInIQ
2/8/2018 - 4:13 PM

shellToSlack.sh

#!/bin/sh

# Run a command; post it and its standard input, output, and error to Slack.
#
# This program expects SLACK_WEBHOOK_URL in its environment.  You can get one
# by creating a new Incoming Webhook at <https://my.slack.com/services/new>.
#
#/ Usage: slack [--attach] [--channel=<channel>] [--stdin] ...
#/   --attach            post to Slack with an attachment (defaults to fixed-width text)
#/   --channel=<channel> post to this Slack channel (defaults to the integration's default channel)
#/   --stdin             capture standard input and include it as a heredoc

set -e

usage() {
    grep "^#/" "$0" | cut -c"4-" >&2
    exit "$1"
}
ATTACH="" CHANNEL="null" STDIN=""
while [ "$#" -gt 0 ]
do
    case "$1" in
        -a|--attach) ATTACH="--attach" shift;;
        -c|--ch|--channel) CHANNEL="\"$2\"" shift 2;;
        -c*) CHANNEL="\"$(echo "$1" | cut -c"3-")\"" shift;;
        --ch=*) CHANNEL="\"$(echo "$1" | cut -d"=" -f"2-")\"" shift;;
        --channel=*) CHANNEL="\"$(echo "$1" | cut -d"=" -f"2-")\"" shift;;
        -s|--stdin) STDIN="--stdin" shift;;
        -h|--help) usage 0;;
        -*) usage 1;;
        *) break;;
    esac
done

TMP="$(mktemp -d "/tmp/slack-XXXXXX")"
trap "rm -rf \"$TMP\"" EXIT INT QUIT TERM

# Run the command and capture standard input, output, and error.
if [ "$STDIN" ]
then

    # Close standard input if it's requested and is a TTY.  This offers
    # some protection against accidentally piping passwords into Slack.
    if [ -t 0 ]
    then
        echo "slack: not reading standard input from a TTY" >&2
        exec <"/dev/null"
    fi

    tee "$TMP/stdin" | "$@" 2>&1 | tee "$TMP/stdout+stderr"
else
    touch "$TMP/stdin"
    "$@" 2>&1 | tee "$TMP/stdout+stderr"
fi

# Produce a properly-quoted command line for posting to Slack.  This should
# be copy-pastable into a shell to run it again.
QUOTED="${SUDO_USER:-"$USER"}@$(hostname)"
if [ "$(whoami)" = "root" ]
then QUOTED="$QUOTED #"
else QUOTED="$QUOTED \$"
fi
for ARG in "$@"
do
    if echo "$ARG" | grep -F -q " "
    then QUOTED="$QUOTED \\\"$ARG\\\""
    else QUOTED="$QUOTED $ARG"
    fi
done

# Clean up quotes, newlines, tabs, and control characters for a JSON string.
jsonify() {
    tr "\n\t" "\036\037" |
    sed "s/$(printf "\036")/\\\\n/g; s/$(printf "\037")/\\\\t/g; s/\"/\\\\\"/g" |
    tr -d "[:cntrl:]"
}

# Construct a JSON payload with attachments for each standard stream.  This
# is not the default behavior because the text isn't fixed-width.
if [ "$ATTACH" ]
then
    cat >"$TMP/data" <<EOF
payload={
    "attachments": [
        {
            "fallback": "$QUOTED",
            "pretext": "$QUOTED",
            "mrkdwn_in": ["fallback", "pretext"],
            "fields": [
EOF
    cat >>"$TMP/data" <<EOF
                {
                    "mrkdwn_in": ["text"],
                    "short": false,
                    "title": "standard input",
                    "value": "$(jsonify <"$TMP/stdin")"
                },
EOF
    cat >>"$TMP/data" <<EOF
                {
                    "mrkdwn_in": ["text"],
                    "short": false,
                    "title": "standard output + standard error",
                    "value": "$(jsonify <"$TMP/stdout+stderr")"
                }
            ]
        }
    ],
    "channel": $CHANNEL,
    "username": "${SUDO_USER:-"$USER"}@$(hostname)"
}
EOF

# Construct a JSON payload the looks a bit like a shell session.  This is
# the default behavior.  If standard input's been captured, it is shown
# in heredoc syntax so it can be copy-pasted.
else
    FENCE='```'
    cat >"$TMP/data" <<EOF
payload={
    "channel": $CHANNEL,
    "mrkdwn": true,
    "text": "$FENCE$QUOTED$(
        if [ -s "$TMP/stdin" ]
        then
            printf " <<EOF\\\\n"
            jsonify <"$TMP/stdin"
            printf "EOF"
        fi
    )\\n$(
        jsonify <"$TMP/stdout+stderr"
    )$FENCE",
    "username": "${SUDO_USER:-"$USER"}@$(hostname)"
}
EOF
fi

# Post to Slack and print the Slack API output to standard error.
printf "slack: " >&2
curl --data-urlencode "$(cat "$TMP/data")" -s "$SLACK_WEBHOOK_URL" >&2
echo >&2