Update Tickets from the Command Line

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.

______________________

Kyle Rankin is SVP of Security and Infrastructure at Zero, the author of many books including Linux Hardening in Hostile Networks, DevOps Troubleshooting and The Official Ubuntu Server Book, and a columnist for Linux Journal. Follow him @kylerankin