Update Tickets from the Command Line
In the April 2017 issue, I wrote about how to use ticketing systems as a sysadmin to organize your tasks better. In that article, I made a brief reference to the fact that I've integrated some of my own scripts with my ticketing system, so here I'm going to talk about a basic bash script I whipped up in a few minutes that can add a comment to a Jira ticket. Although my examples specifically are for use with the Jira ticketing system, you can adapt the same kind of idea to any ticketing system that allows you to interact with it via a web-based API.
One reason many sysadmins dislike ticketing systems is due to the fact that updating and maintaining tickets requires a shift in focus and a break from their regular workflow. To me, tickets are a great way to build an audit trail to show that you've completed a task, and one of the best ways to demonstrate that a task is complete is to paste in the proof of your work into comments in a ticket. Unfortunately, all of that copying and pasting can slow things down and discourages sysadmins from updating their tickets with command output.
My solution to the pain of keeping tickets updated with command output is to
create a script I can pipe output to and have it appear as a comment in the
ticket I specify. My tasks often generate output
into log files, and it's
nice just to pipe those log files into a script and have them show up in a
ticket. I generally use my create_jira_comment
script like this:
$ somecommand | create_jira_ticket -t TICKETNAME
My command may be as simple as an echo
command or something much more
sophisticated. In some cases, I've wanted to wrap the output inside a code
block within the ticket comment and pass along a header to describe what
the code block is, so I've added -C
and
-H
options, respectively:
$ somecommand | create_jira_ticket -t TICKETNAME -C -H "Here
↪is the output from somecommand"
This makes it really easy for an administrator to update a ticket with command output without messing with copying and pasting pages of output into a ticket. The output shows up formatted properly, and when it's in a code block, the ticket shows it in such a way that someone can scroll through it to read the whole thing, but it doesn't fill up a whole page.
Before I get into the Jira-specific bits, let me go over the more generic parts of the script. First, there's the opening section of the script where I define a few variables, set some defaults and source a settings file so I don't have to have the password be hard-coded into this script:
#!/bin/bash
JIRA_HOST="somehost.example.com"
JIRA_USER="someuser"
JIRA_PASS="somepass"
# Set the user and password in a settings file
# instead of in the script
. /etc/default/jira_settings
OUTFILE="/tmp/create_jira_comment-$(date +%Y%m%d-%H%M%S)"
Next, I add a typical usage function (like all good script writers should) to output if someone doesn't use the right syntax with my script:
# Show usage information
usage() {
cat >&2 <<EOF
Usage:
$0 [-h | -t TICKET <-f FILENAME> <-H "Header text">
↪<-F "Footer text"> <-C>]
This script adds a comment to a Jira ticket based on command-line
arguments.
OPTIONS:
-h Show usage information (this message).
-t TICKET The Jira ticket name (ie SA-101)
-f FILENAME A file containing content to past in the Jira
comment (or - to read from pipe)
-H HEADER_TEXT Text to put at the beginning of the comment
-F FOOTER_TEXT Text to put at the end of the comment
-C Wrap comment in a {code} tags
EOF
}
As you can see in the usage output, the script can accept a number of
command-line arguments. The one required option is
-t
, which specifies the
name of the ticket to which you want to add the comment. All of the other options
are optional.
As I started using this script, I realized that I often was piping command
output into this ticket, and the default formatting inside a Jira comment
just made it a huge wall of text. The -C
option will wrap all of the text
into a tag so that it shows up like code and is easier to read. Once I
started wrapping output inside proper formatting, I realized sometimes I
wanted to add text above or below the code block that explained what the
code block was. I added the -H
and
-F
arguments at that point, which let me
specify text to use as a header or footer around the code block.
The next section of the script is where I gather up the command-line options
using the standard getopts
tool:
# Parse Options
while getopts ":t:f:H:F:Ch" flag; do
case "$flag" in
h)
usage
exit 3
;;
t)
TICKET="${OPTARG}"
;;
f)
FILENAME="${OPTARG}"
;;
H)
HEADER="${OPTARG}"
;;
F)
FOOTER="${OPTARG}"
;;
C)
CODETAG='{code}'
;;
\?)
echo "Invalid option: -$OPTARG"
exit 1
;;
:)
echo "Option -$OPTARG requires an argument"
exit 1
;;
esac
done
# Shift past all parsed arguments
shift $((OPTIND-1))
test -z "$TICKET" && usage && echo "No ticket specified!" && exit 1
test -z "$FILENAME" && FILENAME='-'
There's really not all that much to elaborate on with the
getopts
tool. You
can see how I handle the case where a ticket isn't defined and how I set the
default file to be pipe input if the user doesn't set it.
Now that I have all of the options, I can do the actual work of creating the Jira ticket. First, I need to create a file that's formatted in JSON in a way that the JIRA API can accept:
echo -n -e '{\n "body": "' > ${OUTFILE}.json
test -z "$HEADER" || echo -n -e "${HEADER}\n" >> ${OUTFILE}.json
test -z "$CODETAG" || echo -n -e "${CODETAG}\n" >> ${OUTFILE}.json
cat ${FILENAME} | perl -pe 's/\r//g; s/\n/\\r\\n/g; s/\"/\\"/g' >>
↪${OUTFILE}.json
test -z "$CODETAG" || echo -n -e "\n${CODETAG}" >> ${OUTFILE}.json
test -z "$FOOTER" || echo -n -e "\n${FOOTER}" >> ${OUTFILE}.json
echo -e '"\n}' >> ${OUTFILE}.json
You can see in the previous code where I test whether a header, code
argument or footer was defined, and if so, I inject the text or the
appropriate code tags at the right places in the JSON file. That said, the
meat of the formatting is right in the middle where I
cat
the main output
into a series of Perl regular expressions to clean up carriage returns and
newlines in the output and also escape quotes properly. This would be the
section where you'd apply any other cleanup to your output if you noticed it
broke JSON formatting.
Once I have a valid JSON file, I can use curl
to send
it to Jira in a POST
request with the following command:
curl -s -S -u $JIRA_USER:$JIRA_PASS -X POST --data @${OUTFILE}.json -H
↪"Content-Type: application/json"
↪https://$JIRA_HOST/rest/api/latest/issue/${TICKET}/comment
↪2>&1 >> $OUTFILE
if [ $? -ne 0 ]; then
echo "Creating Jira Comment failed"
exit 1
fi
If the command fails, I alert the user, and since I captured the
curl
output in the $OUTFILE log file, I can review it to see what went wrong.
Here is the full script all in one piece:
#!/bin/bash
JIRA_HOST="somehost.example.com"
JIRA_USER="someuser"
JIRA_PASS="somepass"
# Set the user and password in a settings file
# instead of in the script
. /etc/default/prod_release
OUTFILE="/tmp/create_jira_comment-$(date +%Y%m%d-%H%M%S)"
# Show usage information
usage() {
cat >&2 <<EOF
Usage:
$0 [-h | -t TICKET <-f FILENAME> <-H "Header text">
↪<-F "Footer text"> <-C>]
This script adds a comment to a Jira ticket based on
command-line arguments.
OPTIONS:
-h Show usage information (this message).
-t TICKET The Jira ticket name (ie SA-101)
-f FILENAME A file containing content to past in the Jira
↪comment (or - to read from pipe)
-H HEADER_TEXT Text to put at the beginning of the comment
-F FOOTER_TEXT Text to put at the end of the comment
-C Wrap comment in a {code} tags
EOF
}
# Parse Options
while getopts ":t:f:H:F:Ch" flag; do
case "$flag" in
h)
usage
exit 3
;;
t)
TICKET="${OPTARG}"
;;
f)
FILENAME="${OPTARG}"
;;
H)
HEADER="${OPTARG}"
;;
F)
FOOTER="${OPTARG}"
;;
C)
CODETAG='{code}'
;;
\?)
echo "Invalid option: -$OPTARG"
exit 1
;;
:)
echo "Option -$OPTARG requires an argument"
exit 1
;;
esac
done
# Shift past all parsed arguments
shift $((OPTIND-1))
test -z "$TICKET" && usage && echo "No ticket specified!"
↪&& exit 1
test -z "$FILENAME" && FILENAME='-'
echo -n -e '{\n "body": "' > ${OUTFILE}.json
test -z "$HEADER" || echo -n -e "${HEADER}\n" >> ${OUTFILE}.json
test -z "$CODETAG" || echo -n -e "${CODETAG}\n" >> ${OUTFILE}.json
cat ${FILENAME} | perl -pe 's/\r//g; s/\n/\\r\\n/g;
↪s/\"/\\"/g' >> ${OUTFILE}.json
test -z "$CODETAG" || echo -n -e "\n${CODETAG}" >> ${OUTFILE}.json
test -z "$FOOTER" || echo -n -e "\n${FOOTER}" >> ${OUTFILE}.json
echo -e '"\n}' >> ${OUTFILE}.json
curl -s -S -u $JIRA_USER:$JIRA_PASS -X POST --data @${OUTFILE}.json -H
↪"Content-Type: application/json"
↪https://$JIRA_HOST/rest/api/latest/issue/${TICKET}/comment
↪2>&1 >> $OUTFILE
if [ $? -ne 0 ]; then
echo "Creating Jira Comment failed"
exit 1
fi
I've found I now use this script all the time to interact with my ticketing system. In the past, there were times when I could get a bit lazy with archiving proof of work into Jira tickets unless I knew it was truly necessary, but with this script, it's easy, so I find I do it more. In general, I've found if you can make the correct workflow the easiest workflow, your team is more likely to follow it. This script is just one example of how that can work in practice.