Our wee chatty script for Slack

Time to start planning our script for Slack, and the first step is to ask ourselves what do we want out of it. Let's recap our requirements, the script has to do as follows:

  • Accept the text message to display: required
  • Accept a title for the message: required
  • Accept a title_link: optional and only if a title is available
  • Accept a fallback message: required
  • Accept an author_name: optional
  • Accept an author_link: optional and only if an author_link available
  • Accept an author_icon: optional and only if an author_link available
  • Accept a color: required
  • Accept a pretext: optional
  • Accept fields: required and required title, value and short
  • Accept image_URL: optional
  • Accept thumb_URL: optional
  • Accept footer: optional
  • Accept footer_icon: optional and only if the footer is available
  • Accept ts: optional

Now, we have a matrix to start building our bits and to parse the command line, and we are ready to start coding. We know what to do, but due to the complexity of the task, we will proceed step by step, adding bits to bits; and since the core of our message is the JSON, we will start coding its structure. But what to do before the first step? We have to think about which utilities we are going to use. As a start, we would say at least cURL, so check if you have it installed on your system; and if you don't, install it. The first lines of our script will have a sha-bang, and try to locate the utility in our PATH:

#!/bin/bash
# License: GPL

#
# Author: Giorgio Zarrelli <[email protected]>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Retrieve the full path to the system utilities

cURL=$(which cURL)

These first lines resemble the previous Nagios plugin, and this script starts off with a license statement as well. It can be quite useless to state a license, but if we plan to make our scripts available to the public, it is upon us to let the potential user know what they can do with our scripts. The author of this book encourages distributing the software under the GNU GPL license, as it makes it easier to use it to create new programs from it and reutilizing the code. But it is up to the creator of the program what kind of license to use. To have a glance at the various GNU licenses available, we can just go to https://www.gnu.org/licenses/licenses.html and have a look at the numerous licenses on one of them, which will surely fit our purposes. Notice that for this script, we will use lower case variables just to get used to the different kinds of notations adopted by different coders.

So, we pointed to the cURL utility, but how can we be sure that it is installed and we can reach it? Well, we have to bear in mind that the command substitution given to the variable the output of which and this latter will print out something only if the argument passed to it is reachable in one of the directories pointed out by the user $PATH environment variable. Just for a test, let's call which with cURL as an argument:

zarrelli:~$ which cURL
/usr/bin/cURL

Now, let's call which with some blurb:

zarrelli:~$ which cr234a
zarrelli:~$

We get nothing in the output, so our cURL variable will bear no value. One more check, let's test ifconfig as the root:

root:# which ifconfig
/sbin/ifconfig

Then, let's check it again as a non-privileged user:

zarrelli:~$ which ifconfig
zarrelli:~$

So, since the path to ifconfig is in the $PATH variable of the root user only, which, for the non-privileged user, will return nothing. Based on this, we can implement a check with just a few lines:

if [ -z "$cURL" ]
then
echo "Cannot reach the utility, is it in the $PATH or
even installed?"
exit 1
else
echo "The utility is reachable"
fi

So, if the $cURL variable is not empty, the utility is reachable; if not, we receive a warning and exit from the script with an error:

zarrelli:~$ ./my_slack.sh 
The utility is reachable

Great, it seems to work. Now let's change cURL=$(which cURL) into cURL=$(which cur1l2).

Let's run the script again:

zarrelli:~$ /bin/bash -x my_slack.sh 
++ which cur1l2
+ cURL=
+ '[' -z '' ']'
+ echo 'Cannot reach the utility, is it in the $PATH or even in
stalled?'
Cannot reach the utility, is it in the $PATH or even installed?
+ exit 1

Correct, the script exits because which cannot find that non sense string in the user $PATH variable. So, this check can come in handy, but as it is written it is not so useful, so let's make a function out of it:

# Check if which comes back with a path to a utility
check_which()
{
for i in "$@"
do
if [ -z "$i" ];
then
echo "Cannot reach the utility $i, is it in
the $PATH or even installed?"
exit 1
else
:
fi
done
}
check_which "$cURL"

We simply check if which outputs the path to the utility checked; if it outputs nothing, then the variable is empty and we exit with a message and an exit code. If the variable holds something, then we do nothing since we assume this is the path to the utility. This check resembles the one we used for the Nagios plugin:

# Check if we have all the system tools we need
path_exists()
{
for i in "$@"
do
if [ -e "$i" ];
then
(( VERB )) && echo "$i is a valid path"
disk_exists "$DISK"
else
(( VERB )) && echo "$i is not reachable,
is this the correct path?"
echo "SMART UNKNOWN: Please check the
plugin"
exit "$STATE_UNKNOWN"
fi
done
}

This other one check tests if the content of the variable actually points to a file. Which is better? Depends on what you are checking for. If we need to verify that the variable points to a real file, then [ -e "$i" ] is what we are looking for. Otherwise, when we want a more generic check, [ -z "$i" ] will do the job for us.

What do we need next in our script? Let's recall a cURL that we made a few pages ago:

cURL -X POST -H 'Content-type: application/json' --data '{"text": "<!date^1490531695^ {date}|Fallback>"}' https://hooks.slack.com/services/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 

Once we deal with the cURL command, we have to manage the headers:

-X POST
-H 'Content-type: application/json'
--data

These are static and will not change, so we can use them without enclosing in a variable.

We must not forget our WebHook URL:

# WebHook URLwebhook="https://hooks.slack.com/services/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"

Now, it is time to build our first, static payload; and here comes the tricky part. Since the payload is a long multiline JSON, writing it in a single long line would be cumbersome, so we are going to give this burden to a function, which will create this content on our behalf; and it will be nicely formatted too:

generate_payload()
{
cat <<MARKER
{
"attachments": [
{
"fallback": "Text to be displayed in case of client
not supporting formatted text",
"color": "#ff1493",
"pretext": "This goes above the attachment",
"author_name": "Giorgio Zarrelli",
"author_link": "http://www.zarrelli.org",
"author_icon": "https://www.zarrelli.org/blog/wp-content/uploads/2017/03/IMG_20161113_150052.jpg",
"title": "Title example",
"title_link": "http://www.zarrelli.org",
"text": "This text is optional and it is shown in the
attachment",
"fields": [
{
"title": "Priority",
"value": "Medium",
"short": false
}
],
"image_URL":
"http://www.zarrelli.org/path/to/image.jpg",
"thumb_URL": "https://www.zarrelli.org/blog/wp-
content/uploads/2017/03/IMG_20161113_150052-1-e1490610507795.jpg",
"footer": "Slack API",
"footer_icon": "https://www.zarrelli.org/blog/wp-content/uploads/2017/03/IMG_20161113_150052.jpg",
"ts": 1490531695
}
]
}
MARKER
}

We used a here document which, as we saw previously, is one of the best ways to deal with multiline content in Bash scripts. The function will create the content for us, so let's check whether this is true by adding generate_payload at the bottom of our script; and let's run it:

zarrelli:~$ ./my_slack.sh 
{
"attachments": [
{
"fallback": "Text to be displayed in case of client
not supporting formatted text",
"color": "#ff1493",
"pretext": "This goes above the attachment",
"author_name": "Giorgio Zarrelli",
"author_link": "http://www.zarrelli.org",
"author_icon": "https://www.zarrelli.org/blog/wp
content/uploads/2017/03/IMG_20161113_150052.jpg",
"title": "Title example",
"title_link": "http://www.zarrelli.org",
"text": "This text is optional and it is shown in the
attachment",
"fields": [
{
"title": "Priority",
"value": "Medium",
"short": false
}
],
"image_URL":
"http://www.zarrelli.org/path/to/image.jpg",
"thumb_URL": "https://www.zarrelli.org/blog/wp
content/uploads/2017/03/IMG_20161113_150052-1-e1490610507795.jpg",
"footer": "Slack API",
"footer_icon": "https://www.zarrelli.org/blog/wp
content/uploads/2017/03/IMG_20161113_150052.jpg",
"ts": 1490531695
}
]
}

Great, it works! Our content is here, and so we can proceed in building the command line. Let's delete the call to the function at the bottom of the script and add the following line:

"$cURL" -f -X POST -H 'Content-type: application/json' --data "$(generate_payload)" "$webhook" 

$(generate_payload) is a command substitution that will give--data the output generated by the function; but do not forget to enclose it in double quotes, or your output will be taken line by line and not as a single object. Time to save and execute the script and check our #test channel:

zarrelli:~$ ./my_slack.sh 
ok

We checked our script just to be sure that it works as intended.

Nice, the script works, we can see the results in the #test channel and a tiny OK in the command line. Well, nice, but we cannot rely on a third-party output to know if anything went wrong, so let's modify our command line to get some useful response:

"$cURL" -f -X POST -H 'Content-type: application/json' --data "$(generate_payload)" "$webhook" && echo " - Success exit code: $?" || echo "There was an error, exit code: $?"

We added a -f flag to cURL so that it exits silently in case of error, letting us write something meaningful on the output. It is not 100% failsafe and, as we will see, sometimes an error message slips through, but it is still usable. Then we add this:

&& echo " - Success exit code: $?" || echo "There was an error, 
exit code: $?"

We have already seen this kind of test before. We are checking if the command was successful or not and echoing the exit code called "$?" to the stdout. Let's have a look:

zarrelli:~$ ./my_slack.sh 
ok - Success exit code: 0

Great! cURL just printed ok on the command line, and since the execution went fine, we printed a Success message with exit code. Now, let's remove k from $webhook on the last line and execute the script again:

zarrelli:~$ ./my_slack.sh 
cURL: (3) <URL> malformed

There was an error, exit code: 3.

It should have failed silently, but anyway, we were able to write our meaningful error message; and this is all we want.

Our first step is accomplished: we can send a static message to our WebHook and have the message displayed in the #test channel. This is interesting, but not so flexible. What we really want is to be able to modify the message based on our input. To reach this goal, we have to turn into variables all the bits inside our attachment so that we will be able to pass the values on the command line. Let's start creating a couple of variables:

# Message attachment variables
fallback=${fallback:="This is a text shown on older clients"}
text=${text:="This line of text is optional"}

Now, we just have to modify the payload:

{
"fallback": "$fallback",
"color": "#ff1493",
"pretext": "This goes above the attachment",
"author_name": "Giorgio Zarrelli",
"author_link": "http://www.zarrelli.org",
"author_icon": "https://www.zarrelli.org/
blog/wp-content/uploads/2017/03/IMG_20161113_150052.jpg",
"title": "Title example",
"title_link": "http://www.zarrelli.org",
"text": "$text",
"fields": [
{
"title": "Priority",
"value": "Medium",
"short": false
}
],
"image_URL":
"http://www.zarrelli.org/path/to/image.jpg",
"thumb_URL": "https://www.zarrelli.org/
blog/wpcontent/uploads/2017/03/IMG_20
161113_150052-1-e1490610507795.jpg",
"footer": "Slack API",
"footer_icon": "https://www.zarrelli.org/blog/wpcontent/
uploads/2017/03/IMG_20161113_150052.jpg",
"ts": 1490531695
}

Now, let's run the script to see if our modifications are taken into account:

zarrelli:~$ ./my_slack.sh 
ok - Success exit code: 0

It seems it worked; a check to the #test channel will confirm the outcome, as we can see in the following screenshot:


Our variables are being taken into account by the script.

Since our variables seem to be working, let's create a new whole bunch of them:

# Message attachment variables
fallback=${fallback:="This is a text shown on older clients"}
color=${color:="good"}
pretext=${pretext:="Announcement:"}
author_name=${author_name:="Giorgio Zarrelli"}
author_link=${author_link:="http://www.zarrelli.org"}
author_icon=${author_icon:="https://www.zarrelli.org/blog/wp
content/uploads/2017/03/IMG_20161113_150052.jpg"}
title=${title:="New message"}
title_link=${title_link:="Announcement:"}
text=${text:="This line of text is optional"}
fields_title=${fields_title:="Priority"}
fields_value=${fields_value:="Medium"}
fields_short=${fields_short:="true"}
im-
age_URL=${image_URL:="http://www.zarrelli.org/path/to/image.jpg"}
thumb_URL=${thumb_URL:="https://www.zarrelli.org/blog/wp-content/uploads/2017/03/IMG_20161113_150052-1-e1490610507795.jpg"}
footer=${footer:="Mastering Bash"}
footer_icon=${footer_icon:="https://www.zarrelli.org/blog/wp-content/uploads/2017/03/IMG_20161113_150052.jpg"}
ts=${ts:="1490531695"}

Obviously, the payload must be modified accordingly:

 {
"fallback": "$fallback",
"color": "$color",
"pretext": "$pretext",
"author_name": "$author_name",
"author_link": "$author_link",
"author_icon": "$author_icon",
"title": "$title",
"title_link": "$title_link",
"text": "$text",
"fields": [
{
"title": "$fields_title",
"value": "$fields_value",
"short": "fields_short"
}
],
"image_URL": "$image_URL",
"thumb_URL": "$thumb_URL",
"footer": "$footer",
"footer_icon": "$footer_icon",
"ts": "$ts"
}

Again, let's test our code:

zarrelli:~$ ./my_slack.sh 
ok - Success exit code: 0

The screenshot here shows our newly formatted message:

It seems that our new payload works well.

Now that we have all the variables in place, it is time to create a menu that will help us manage the user input. Let's start writing a help function; we already saw how to do this in the previous chapter, but this time, we have a lot of options to deal with, so we start associating a switch to each variable:

-f --fallback
-c --color
-p --pretext
-an --author_name
-al --author_link
-ai --author_icon
-t --title
-tl --title_link
-tx --text
-ft --fields_title
-fv --fields_value
-fs --fields_short
-iu --image_URL
-tu --thumb_URL
-fr --footer
-fi --footer_icon
-ts –-timestamp

What we use as short and long options is up to us, but we must keep in mind a golden rule: these must be meaningful not for us, but for the potential users of the script, so we have to take a step aside and try to think as our users. Once we have decided on the best options, we must proceed creating the actual command-line parser:

# Parse parameters on the command line
while (( $# > 0 ))
do
case "$1" in
-h | --help)
print_help
exit 1
;;
-f | --fallback)
shift
fallback="$1"
;;
-c | --color)
shift
color="$1"
;;
-p | -pretext)
shift
pretext="$1"
;;
-an | --author_name)
shift
author_name="$1"
;;
-al | --author_link)
shift
author_link="$1"
;;
-ai | --author_icon)
shift
author_icon="$1"
;;
-t | --title)
shift

;;
-tl | --title_link)
shift
title_link="$1"
;;
-tx | --text)
shift
author_icon="$1"
;;
-ft | --fields_title)
shift
fields_
;;
-fv | --fields_value)
shift
fields_value="$1"
;;
-fs | --fields_title)
shift
fields_short="$1"
;;
-iu | --image_URL)
shift
image_URL="$1"
;;
-tu | --thumb_URL)
shift
thumb_URL="$1"
;;
-fr | --footer)
shift
footer="$1"
;;
-fi | --footer_icon)
shift
image_URL="$1"
;;
-ts | --timestamp)
shift
ts="$1"
;;
*) echo "Unknown argument: $1"
print_help
exit 1
;;
esac
shift
done

And now the print_help function:

# Print help and usage
print_help()
{
cat << HERE

Slack sender v1.0
---------------
Please enter one or more of the following options:
-h | --help
-f | --fallback
-c | --color
-p | -pretext
-an | --author_name
-al | --author_link
-ai | --author_icon
-t | --title
-tl | --title_link
-tx | --text
-ft | --fields_title
-fv | --fields_value
-fs | --fields_title
-iu | --image_URL
-tu | --thumb_URL
-fr | --footer
-fi | --footer_icon
-ts | --timestamp
HERE
}

Now, let's comment the last line of the script: the one calling cURL. Then, let's call the script with the -h switch:

zarrelli:~$ ./my_slack.sh -h

Slack sender v1.0
-------------------
Please enter one or more of the following options:
-h | --help
-f | --fallback
-c | --color
-p | -pretext
-an | --author_name
-al | --author_link
-ai | --author_icon
-t | --title
-tl | --title_link
-tx | --text
-ft | --fields_title
-fv | --fields_value
-fs | --fields_title
-iu | --image_URL
-tu | --thumb_URL
-fr | --footer
-fi | --footer_icon
-ts | --timestamp

There is a small issue: even the Nagios plugin had it, so it is time to solve it. Let's call the script without any parameters:

zarrelli:~$ ./my_slack.sh -h
zarrelli:~$

Nothing, we do not have anything as feedback, so we do not even know if we can use a -h switch to get more information. How do we solve this hindrance? Well, we have different options available; we could modify the while clause for instance or adopt a different strategy:

if (( $# == 0 ))
then
echo "No options provided, you can use -h for help but
this time I will do it for you..."
print_help
fi

We have to put these lines before the menu creation, and they will check the input: if nothing is given on the command line, it will write an error message and call the print_help function. Time to test our script now and see what happens:

./my_slack.sh -c warning -an Me -t "My cli test" -tx "This is a cli text"
ok - Success exit code: 0

It seems it worked, and the screenshot of the #test channel confirms our guess:


Our script now accepts the command-line parameters.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.34.146