Robware Software by Rob

Automatically making tickets in Jira for updating .NET packages

Keeping your project dependencies up to date over the lifetime of a project can be a real burden, especially when you're working as part of a team. In order to solve the problem of who takes ownership of keeping packages up to date, I sought to create a way of automatically creating tickets in Jira, my current team's project management tool.

To achieve this I decided to have a bash script run as a scheduled job within GitLab (my teams's current source control and CI/CD tool).

Here is the script to do it (warning, I'm not much of a bash scripter):

#!/bin/bash

# Environment variables:
# JIRA_USERNAME: The user which will be making the tickets
# JIRA_API_TOKEN: API token for the user - https://support.atlassian.com/atlassian-account/docs/manage-api-tokens-for-your-atlassian-account/
# PROJECT: The Jira initialism for your project
# ISSUE_TYPE: What type of issue it is (E.g "Task")
# STATUS_ID: Which column it should end up in (E.g "To Do", "Refined") - https://<jira-instance>.atlassian.net/rest/api/3/issue/<issue ID>/transitions where <issue ID> is an ID for an issue which belongs to the workflow you're targetting
# RANK_ABOVE_ISSUE (optional): Jira ticket to rank the new tickets above
# PROJECT_NAME (optional): For the ticket title. Will search for solution file if not set
# IGNORE_PACKAGES (optional): A space separated list of packages to ignore

dotnet restore 1> /dev/null

output=`dotnet list package --outdated | grep \>`

IFS=\>
fullPackageList=($output)

IFS=' '
ignore=($IGNORE_PACKAGES)
packages=()
for element in "${fullPackageList[@]}"
do
	details=($element)
	if [[ ! "${ignore[*]}" =~ "${details[0]}" ]]; then
		packages=("${packages[@]}" "${details[0]}")
	fi
done

uniquePackages=($(for p in "${packages[@]}"; do echo "${p}"; done | sort -u))

if ((${#uniquePackages[@]} > 0)); then
	echo "${uniquePackages[@]}"

	if [ -z "$PROJECT_NAME" ]; then
		filename=`ls *.sln`
		PROJECT_NAME="${filename%.*}"
	fi
	title="Update dependencies for $PROJECT_NAME"

	existingStoryResult=$(curl -k -X GET \
		-u "$JIRA_USERNAME:$JIRA_API_TOKEN" \
		-H "Content-Type: application/json" \
		--data-urlencode 'jql=project='"$PROJECT"' and statusCategory!=Done and summary~"'"$title"'"'\
		-L 'https://<jira-instance>.atlassian.net/rest/api/3/search?jql=project%3D'"$PROJECT"'%20and%20statusCategory!%3DDone%20and%20summary~"'"${title// /%20}"'"')

	if [[ "$existingStoryResult" == '{"startAt":0,"maxResults":50,"total":0,"issues":[]}' ]]; then
		echo "Creating new story"
		createResult=$(curl -k -X POST \
			-u "$JIRA_USERNAME:$JIRA_API_TOKEN" \
			-H "Content-Type: application/json" \
			-d '{"fields":{"project":{"key":"'"$PROJECT"'"},"summary":"'"$title"'","description":{"content":[{"content":[{"text":"'"${uniquePackages[@]//$'\n'/\\n}"'","type":"text"}],"type":"codeBlock"}],"type":"doc","version":1},"issuetype":{"name":"'"$ISSUE_TYPE"'"}},"transition":{"id":"'"$STATUS_ID"'"}}' \
			-L "https://<jira-instance>.atlassian.net/rest/api/3/issue/")

		if [ ! -z "$RANK_ABOVE_ISSUE" ]; then
			echo "Ranking story"
			issueId=$(jq -r ".id" <<< $createResult)
			curl -k -X PUT \
				-u "$JIRA_USERNAME:$JIRA_API_TOKEN" \
				-H "Content-Type: application/json" \
				-d '{"rankBeforeIssue":"'"$RANK_ABOVE_ISSUE"'","issues":["'"$issueId"'"]}' \
				-L "https://<jira-instance>.atlassian.net/rest/agile/1.0/issue/rank"
		fi
	else
		echo "Existing story found, not posting new one"
	fi
else 
	echo "Up to date"
fi

A simple rundown of the steps in this script:

  • dotnet restore to get all current packages
  • Get the list of outdated packages
  • Format the output in to an array of package names
  • Pick out all the unique packages, since this could get the same package across multiple projects
  • Find the project (solution) name, unless it's been specified
  • Search for an existing ticket, end here if found
  • Create a ticket to update the dependencies
  • Put it in the backlog in the specified column
  • Rank the ticket, if specified

The script is run every morning. This means that, if there are updates to be done, we'll have a ticket at the ready for anyone to pick up.

If you want to use this in GitLab, here's the config for it:

check-for-updates:
  image: mcr.microsoft.com/dotnet/sdk:<version number>
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
  before_script:
    - chmod +x $SCRIPT_DIR/check-for-updates.sh
  script:
    - if [ ! -z "$SOURCE_DIRECTORY" ]; then cd $SOURCE_DIRECTORY; fi
    - $SCRIPT_DIR/check-for-updates.sh

Where $SCRIPT_DIR and $SOURCE_DIR are where the script and source code live respectively.

If you want to run this as a scheduled job, it's a good idea to make sure your normal build steps don't run on the schedule:

job:
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
      when: never
Posted on Friday the 16th of June 2023