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