Skip to content

feat(slack-deploy-notification): include version + diff since last prod release #91

feat(slack-deploy-notification): include version + diff since last prod release

feat(slack-deploy-notification): include version + diff since last prod release #91

name: Jira Ticket Check
on:
pull_request:
types: [opened, synchronize, reopened, edited]
workflow_call:
inputs:
jira-project-pattern:
required: false
type: string
description: 'Regex pattern for Jira project keys (e.g., "DEVEX|DEV|PROJ")'
default: '[A-Z]+'
check-commits:
required: false
type: boolean
description: 'Whether to check commit messages for Jira tickets'
default: true
fail-on-missing:
required: false
type: boolean
description: 'Whether to fail the workflow if no Jira ticket is found'
default: true
verify-ticket-exists:
required: false
type: boolean
description: 'Whether to verify the ticket exists in Jira via API'
default: true
jira-instance:
required: false
type: string
description: 'Jira instance URL (e.g., revolutionparts.atlassian.net)'
default: 'revolutionparts.atlassian.net'
secrets:
JIRA_GITHUB_USER_EMAIL:
required: false
description: 'Jira account email for API authentication'
JIRA_GITHUB_USER_API_TOKEN:
required: false
description: 'Jira API token for authentication'
jobs:
check-jira-ticket:
name: Verify Jira Ticket Link
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Extract Jira ticket from branch name
id: branch-check
env:
BRANCH_NAME: ${{ github.head_ref }}
JIRA_PROJECT_PATTERN: ${{ inputs.jira-project-pattern }}
run: |
PATTERN="$JIRA_PROJECT_PATTERN"
if [ -z "$PATTERN" ]; then
PATTERN="[A-Za-z]+"
else
# Make pattern case-insensitive by adding both cases if it's a simple pattern
PATTERN=$(echo "$PATTERN" | sed 's/\[A-Z\]\+/[A-Za-z]+/g' | sed 's/\[A-Z\]/[A-Za-z]/g')
fi
JIRA_PATTERN="${PATTERN}-[0-9]+"
if [ -z "$BRANCH_NAME" ]; then
echo "ticket=" >> $GITHUB_OUTPUT
echo "found_in=" >> $GITHUB_OUTPUT
exit 0
fi
# Case-insensitive search
if echo "$BRANCH_NAME" | grep -qiE "$JIRA_PATTERN"; then
TICKET=$(echo "$BRANCH_NAME" | grep -oiE "$JIRA_PATTERN" | head -1)
# Normalize to uppercase for Jira API (Jira ticket keys are uppercase)
TICKET=$(echo "$TICKET" | tr '[:lower:]' '[:upper:]')
echo "ticket=$TICKET" >> $GITHUB_OUTPUT
echo "found_in=branch" >> $GITHUB_OUTPUT
echo "✅ Found Jira ticket in branch name: $TICKET"
exit 0
fi
echo "ticket=" >> $GITHUB_OUTPUT
echo "found_in=" >> $GITHUB_OUTPUT
- name: Extract Jira ticket from PR title
id: pr-title-check
if: steps.branch-check.outputs.ticket == ''
env:
PR_TITLE: ${{ github.event.pull_request.title }}
JIRA_PROJECT_PATTERN: ${{ inputs.jira-project-pattern }}
run: |
PATTERN="$JIRA_PROJECT_PATTERN"
if [ -z "$PATTERN" ]; then
PATTERN="[A-Za-z]+"
else
# Make pattern case-insensitive by adding both cases if it's a simple pattern
PATTERN=$(echo "$PATTERN" | sed 's/\[A-Z\]\+/[A-Za-z]+/g' | sed 's/\[A-Z\]/[A-Za-z]/g')
fi
JIRA_PATTERN="${PATTERN}-[0-9]+"
if [ -z "$PR_TITLE" ]; then
echo "ticket=" >> $GITHUB_OUTPUT
echo "found_in=" >> $GITHUB_OUTPUT
exit 0
fi
# Case-insensitive search
if echo "$PR_TITLE" | grep -qiE "$JIRA_PATTERN"; then
TICKET=$(echo "$PR_TITLE" | grep -oiE "$JIRA_PATTERN" | head -1)
# Normalize to uppercase for Jira API (Jira ticket keys are uppercase)
TICKET=$(echo "$TICKET" | tr '[:lower:]' '[:upper:]')
echo "ticket=$TICKET" >> $GITHUB_OUTPUT
echo "found_in=pr_title" >> $GITHUB_OUTPUT
echo "✅ Found Jira ticket in PR title: $TICKET"
exit 0
fi
echo "ticket=" >> $GITHUB_OUTPUT
echo "found_in=" >> $GITHUB_OUTPUT
- name: Extract Jira ticket from PR description
id: pr-description-check
if: steps.pr-title-check.outputs.ticket == ''
env:
PR_BODY: ${{ github.event.pull_request.body }}
JIRA_PROJECT_PATTERN: ${{ inputs.jira-project-pattern }}
run: |
PATTERN="$JIRA_PROJECT_PATTERN"
if [ -z "$PATTERN" ]; then
PATTERN="[A-Za-z]+"
else
# Make pattern case-insensitive by adding both cases if it's a simple pattern
PATTERN=$(echo "$PATTERN" | sed 's/\[A-Z\]\+/[A-Za-z]+/g' | sed 's/\[A-Z\]/[A-Za-z]/g')
fi
JIRA_PATTERN="${PATTERN}-[0-9]+"
if [ -z "$PR_BODY" ]; then
echo "ticket=" >> $GITHUB_OUTPUT
echo "found_in=" >> $GITHUB_OUTPUT
exit 0
fi
# Case-insensitive search
if echo "$PR_BODY" | grep -qiE "$JIRA_PATTERN"; then
TICKET=$(echo "$PR_BODY" | grep -oiE "$JIRA_PATTERN" | head -1)
# Normalize to uppercase for Jira API (Jira ticket keys are uppercase)
TICKET=$(echo "$TICKET" | tr '[:lower:]' '[:upper:]')
echo "ticket=$TICKET" >> $GITHUB_OUTPUT
echo "found_in=pr_description" >> $GITHUB_OUTPUT
echo "✅ Found Jira ticket in PR description: $TICKET"
exit 0
fi
echo "ticket=" >> $GITHUB_OUTPUT
echo "found_in=" >> $GITHUB_OUTPUT
- name: Extract Jira ticket from commits
id: commit-check
if: steps.pr-description-check.outputs.ticket == ''
env:
CHECK_COMMITS: ${{ inputs.check-commits }}
JIRA_PROJECT_PATTERN: ${{ inputs.jira-project-pattern }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
if [ "$CHECK_COMMITS" != "true" ]; then
echo "ticket=" >> $GITHUB_OUTPUT
echo "found_in=" >> $GITHUB_OUTPUT
exit 0
fi
PATTERN="$JIRA_PROJECT_PATTERN"
if [ -z "$PATTERN" ]; then
PATTERN="[A-Z]+"
fi
JIRA_PATTERN="${PATTERN}-[0-9]+"
if [ -z "$BASE_SHA" ] || [ -z "$HEAD_SHA" ]; then
echo "ticket=" >> $GITHUB_OUTPUT
echo "found_in=" >> $GITHUB_OUTPUT
exit 0
fi
# Get all commit messages between base and head
COMMIT_MESSAGES=$(git log --pretty=format:"%s" ${BASE_SHA}..${HEAD_SHA} 2>/dev/null || echo "")
# Case-insensitive search
if [ -n "$COMMIT_MESSAGES" ] && echo "$COMMIT_MESSAGES" | grep -qiE "$JIRA_PATTERN"; then
TICKET=$(echo "$COMMIT_MESSAGES" | grep -oiE "$JIRA_PATTERN" | head -1)
# Normalize to uppercase for Jira API (Jira ticket keys are uppercase)
TICKET=$(echo "$TICKET" | tr '[:lower:]' '[:upper:]')
echo "ticket=$TICKET" >> $GITHUB_OUTPUT
echo "found_in=commits" >> $GITHUB_OUTPUT
echo "✅ Found Jira ticket in commit messages: $TICKET"
exit 0
fi
echo "ticket=" >> $GITHUB_OUTPUT
echo "found_in=" >> $GITHUB_OUTPUT
- name: Determine final ticket
id: final-ticket
run: |
if [ -n "${{ steps.branch-check.outputs.ticket }}" ]; then
echo "ticket=${{ steps.branch-check.outputs.ticket }}" >> $GITHUB_OUTPUT
echo "found_in=${{ steps.branch-check.outputs.found_in }}" >> $GITHUB_OUTPUT
elif [ -n "${{ steps.pr-title-check.outputs.ticket }}" ]; then
echo "ticket=${{ steps.pr-title-check.outputs.ticket }}" >> $GITHUB_OUTPUT
echo "found_in=${{ steps.pr-title-check.outputs.found_in }}" >> $GITHUB_OUTPUT
elif [ -n "${{ steps.pr-description-check.outputs.ticket }}" ]; then
echo "ticket=${{ steps.pr-description-check.outputs.ticket }}" >> $GITHUB_OUTPUT
echo "found_in=${{ steps.pr-description-check.outputs.found_in }}" >> $GITHUB_OUTPUT
elif [ -n "${{ steps.commit-check.outputs.ticket }}" ]; then
echo "ticket=${{ steps.commit-check.outputs.ticket }}" >> $GITHUB_OUTPUT
echo "found_in=${{ steps.commit-check.outputs.found_in }}" >> $GITHUB_OUTPUT
else
echo "ticket=" >> $GITHUB_OUTPUT
echo "found_in=" >> $GITHUB_OUTPUT
fi
- name: Fail if no Jira ticket found
if: steps.final-ticket.outputs.ticket == ''
env:
FAIL_ON_MISSING: ${{ inputs.fail-on-missing }}
CHECK_COMMITS: ${{ inputs.check-commits }}
BRANCH_NAME: ${{ github.head_ref }}
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
if [ "$FAIL_ON_MISSING" != "true" ] && [ -n "$FAIL_ON_MISSING" ]; then
echo "⚠️ No Jira ticket found, but fail-on-missing is disabled"
exit 0
fi
# Default to true if not specified (for direct PR triggers)
if [ -z "$FAIL_ON_MISSING" ]; then
FAIL_ON_MISSING="true"
fi
if [ "$FAIL_ON_MISSING" != "true" ]; then
exit 0
fi
echo "❌ No Jira ticket found in branch name, PR title, PR description"
if [ "$CHECK_COMMITS" == "true" ]; then
echo " or commit messages."
else
echo "."
fi
echo ""
echo "Please ensure one of the following contains a Jira ticket key (e.g., DEVEX-600, DEV-123):"
echo " - Branch name: $BRANCH_NAME"
echo " - PR title: $PR_TITLE"
echo " - PR description"
if [ "$CHECK_COMMITS" == "true" ]; then
echo " - Commit messages"
fi
echo ""
echo "Jira ticket format: [PROJECT-KEY]-[NUMBER] (e.g., DEVEX-600)"
exit 1
- name: Verify ticket exists in Jira
id: verify-ticket
if: steps.final-ticket.outputs.ticket != ''
env:
VERIFY_TICKET: ${{ inputs.verify-ticket-exists }}
JIRA_EMAIL: ${{ secrets.JIRA_GITHUB_USER_EMAIL }}
JIRA_TOKEN: ${{ secrets.JIRA_GITHUB_USER_API_TOKEN }}
TICKET: ${{ steps.final-ticket.outputs.ticket }}
JIRA_INSTANCE: ${{ inputs.jira-instance }}
run: |
# Default to true if not specified (for direct PR triggers or when default is used)
if [ -z "$VERIFY_TICKET" ]; then
VERIFY_TICKET="true"
fi
if [ "$VERIFY_TICKET" = "false" ]; then
echo "⚠️ Ticket verification is disabled, skipping API check"
echo "exists=true" >> $GITHUB_OUTPUT
exit 0
fi
if [ -z "$JIRA_EMAIL" ] || [ -z "$JIRA_TOKEN" ]; then
echo "⚠️ Jira credentials not provided, skipping ticket verification"
echo "⚠️ To enable verification, provide JIRA_GITHUB_USER_EMAIL and JIRA_GITHUB_USER_API_TOKEN secrets"
echo "exists=true" >> $GITHUB_OUTPUT
exit 0
fi
if [ -z "$JIRA_INSTANCE" ]; then
JIRA_INSTANCE="revolutionparts.atlassian.net"
fi
# Create base64 encoded credentials
AUTH_STRING=$(echo -n "${JIRA_EMAIL}:${JIRA_TOKEN}" | base64)
# Validate variables before making the request
if [ -z "$TICKET" ]; then
echo "❌ TICKET variable is empty"
exit 1
fi
echo "🔍 Verifying ticket $TICKET exists in Jira..."
# Construct URL and validate it - only request fields we need (status) to avoid processing ticket description
API_URL="https://${JIRA_INSTANCE}/rest/api/3/issue/${TICKET}?fields=status"
# Make API request to check if ticket exists
# Force HTTP/1.1 to avoid HTTP/2 issues that can cause "Failed sending HTTP request"
# Use a temporary file to safely store the response
TEMP_FILE=$(mktemp)
HTTP_CODE=$(curl -s \
--http1.1 \
-X GET \
-H "Authorization: Basic ${AUTH_STRING}" \
-H "Accept: application/json" \
--max-time 30 \
-w "%{http_code}" \
-o "${TEMP_FILE}" \
"${API_URL}" 2>&1 | tail -1)
# Read response body from temp file
BODY=$(cat "${TEMP_FILE}" 2>/dev/null || echo "")
rm -f "${TEMP_FILE}"
# Check HTTP status code first
if [ "$HTTP_CODE" = "404" ]; then
echo "❌ Ticket $TICKET does not exist in Jira"
echo "exists=false" >> $GITHUB_OUTPUT
exit 1
fi
if [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "403" ]; then
echo "⚠️ Authentication failed or insufficient permissions to verify ticket"
echo "⚠️ Continuing without verification..."
echo "exists=false" >> $GITHUB_OUTPUT
exit 0
fi
# Check if response indicates ticket doesn't exist (error in JSON)
if echo "$BODY" | grep -qiE '"errorMessages"'; then
echo "❌ Ticket $TICKET does not exist in Jira"
echo "exists=false" >> $GITHUB_OUTPUT
exit 1
fi
# If we get here, assume the ticket exists and try to extract status
echo "✅ Ticket $TICKET exists in Jira"
echo "exists=true" >> $GITHUB_OUTPUT
# Extract status from Jira API response using jq (safest method)
# Use --raw-output to get plain text, and handle errors properly
if command -v jq >/dev/null 2>&1; then
STATUS=$(printf '%s' "$BODY" | jq -r '.fields.status.name // empty' 2>/dev/null || echo "")
fi
# Fallback: extract status using grep/sed pattern matching (only if jq fails)
if [ -z "$STATUS" ]; then
# Use printf instead of echo to avoid interpretation of escape sequences
STATUS=$(printf '%s' "$BODY" | grep -o '"status"[^}]*"name":"[^"]*' | sed 's/.*"name":"\([^"]*\)".*/\1/' | head -1 || echo "")
fi
if [ -z "$STATUS" ]; then
# Alternative: look for any "name" field that appears after "status" in the JSON
STATUS=$(printf '%s' "$BODY" | sed -n 's/.*"status"[^}]*"name":"\([^"]*\)".*/\1/p' | head -1 || echo "")
fi
# Normalize status to lowercase for comparison
STATUS_LOWER=$(echo "$STATUS" | tr '[:upper:]' '[:lower:]')
# Check if status is in the list of invalid statuses
INVALID_STATUSES="backlog|open|closed|done|ready to release"
if echo "$STATUS_LOWER" | grep -qiE "($INVALID_STATUSES)"; then
echo "❌ Ticket $TICKET is in an invalid status: $STATUS"
echo ""
echo "Tickets in the following statuses cannot have work logged against them:"
echo " - Backlog"
echo " - Open"
echo " - Closed/Done"
echo " - Ready to Release"
echo ""
echo "Please move the ticket to an active status before creating a PR."
exit 1
fi
if [ -n "$STATUS" ]; then
echo " Status: $STATUS"
fi
- name: Fail if ticket does not exist
if: steps.final-ticket.outputs.ticket != '' && steps.verify-ticket.outputs.exists == 'false'
env:
TICKET: ${{ steps.final-ticket.outputs.ticket }}
JIRA_INSTANCE: ${{ inputs.jira-instance }}
run: |
if [ -z "$JIRA_INSTANCE" ]; then
JIRA_INSTANCE="revolutionparts.atlassian.net"
fi
echo "❌ Jira ticket $TICKET does not exist in Jira"
echo ""
echo "The ticket key was found in your PR, but it does not exist in Jira."
echo "Please ensure you're using a valid Jira ticket key."
echo ""
echo "Link: https://${JIRA_INSTANCE}/browse/$TICKET"
exit 1
- name: Success message
if: steps.final-ticket.outputs.ticket != '' && (steps.verify-ticket.outputs.exists == 'true' || steps.verify-ticket.outputs.exists == '')
env:
TICKET: ${{ steps.final-ticket.outputs.ticket }}
JIRA_INSTANCE: ${{ inputs.jira-instance }}
FOUND_IN: ${{ steps.final-ticket.outputs.found_in }}
run: |
if [ -z "$JIRA_INSTANCE" ]; then
JIRA_INSTANCE="revolutionparts.atlassian.net"
fi
echo "✅ Jira ticket found: $TICKET"
echo " Found in: $FOUND_IN"
echo " Link: https://${JIRA_INSTANCE}/browse/$TICKET"