feat(slack-deploy-notification): include version + diff since last prod release #91
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |