Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/code-scans.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
schedule:
- cron: '0 6 * * 1'
- cron: '0 12 * * 5'

concurrency:
group: sdle-${{ github.event.pull_request.number || github.ref }}
Expand Down
37 changes: 35 additions & 2 deletions .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,43 @@ jobs:
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"

- name: Classify update type
id: classify
env:
FM_UPDATE_TYPE: ${{ steps.metadata.outputs.update-type }}
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
UPDATE_TYPE="${FM_UPDATE_TYPE}"

if [[ -z "$UPDATE_TYPE" || "$UPDATE_TYPE" == "null" ]]; then
if [[ "$PR_TITLE" =~ ([0-9]+)\.([0-9]+)\.([0-9]+).*to.*([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
OLD_MAJOR="${BASH_REMATCH[1]}"
OLD_MINOR="${BASH_REMATCH[2]}"
OLD_PATCH="${BASH_REMATCH[3]}"
NEW_MAJOR="${BASH_REMATCH[4]}"
NEW_MINOR="${BASH_REMATCH[5]}"
NEW_PATCH="${BASH_REMATCH[6]}"

if [[ "$OLD_MAJOR" != "$NEW_MAJOR" ]]; then
UPDATE_TYPE="version-update:semver-major"
elif [[ "$OLD_MINOR" != "$NEW_MINOR" ]]; then
UPDATE_TYPE="version-update:semver-minor"
elif [[ "$OLD_PATCH" != "$NEW_PATCH" ]]; then
UPDATE_TYPE="version-update:semver-patch"
else
UPDATE_TYPE="unknown"
fi
else
UPDATE_TYPE="unknown"
fi
fi

echo "update-type=$UPDATE_TYPE" >> "$GITHUB_OUTPUT"

- name: Enable auto-merge for minor and patch updates
if: |
steps.metadata.outputs.update-type == 'version-update:semver-patch' ||
steps.metadata.outputs.update-type == 'version-update:semver-minor'
steps.classify.outputs.update-type == 'version-update:semver-patch' ||
steps.classify.outputs.update-type == 'version-update:semver-minor'
run: gh pr merge --auto --squash "$PR_URL"
env:
PR_URL: ${{ github.event.pull_request.html_url }}
Expand Down
257 changes: 178 additions & 79 deletions .github/workflows/dependabot-gchat-notify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ name: Notify Google Chat on Dependabot Events

on:
pull_request_target:
types: [opened, closed]
types: [opened]
pull_request:
types: [closed]
push:
branches: [main]

permissions:
contents: read
Expand All @@ -12,113 +16,208 @@ jobs:
notify:
name: Post Dependabot event to Google Chat
runs-on: ubuntu-latest
if: github.actor == 'dependabot[bot]'
# Trigger conditions:
# - opened/closed events: only when the PR was authored by dependabot[bot]
# - push events: only when the head commit looks like a Dependabot squash
# (commit message starts with chore(deps- which is our configured prefix)
if: |
(github.event_name != 'push' && github.event.pull_request.user.login == 'dependabot[bot]') ||
(github.event_name == 'push' && startsWith(github.event.head_commit.message, 'chore(deps-'))
steps:
- name: Fetch Dependabot metadata
id: meta
- name: Resolve PR for push event
id: pr_lookup
if: github.event_name == 'push'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMMIT_SHA: ${{ github.event.head_commit.id }}
run: |
# Find the PR that produced this squash commit on main.
PR_JSON=$(gh api "repos/${{ github.repository }}/commits/${COMMIT_SHA}/pulls" 2>/dev/null | jq '[.[] | select(.user.login == "dependabot[bot]")] | .[0] // empty')
if [[ -z "$PR_JSON" || "$PR_JSON" == "null" ]]; then
echo "No Dependabot PR associated with this commit; skipping."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "pr_number=$(echo "$PR_JSON" | jq -r '.number')" >> "$GITHUB_OUTPUT"
echo "pr_url=$(echo "$PR_JSON" | jq -r '.html_url')" >> "$GITHUB_OUTPUT"
echo "pr_title=$(echo "$PR_JSON" | jq -r '.title')" >> "$GITHUB_OUTPUT"
echo "pr_head_ref=$(echo "$PR_JSON" | jq -r '.head.ref')" >> "$GITHUB_OUTPUT"

- name: Stop early if push had no Dependabot PR
if: github.event_name == 'push' && steps.pr_lookup.outputs.skip == 'true'
run: exit 0

- name: Fetch Dependabot metadata (PR events)
id: meta_pr
if: github.event_name != 'push'
uses: dependabot/fetch-metadata@v2
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"

- name: Fetch Dependabot metadata (push events)
id: meta_push
if: github.event_name == 'push' && steps.pr_lookup.outputs.skip != 'true'
uses: dependabot/fetch-metadata@v2
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"
pr-number: ${{ steps.pr_lookup.outputs.pr_number }}

- name: Classify event
- name: Classify event and update type
id: classify
env:
FM_UPDATE_TYPE: ${{ steps.meta_pr.outputs.update-type || steps.meta_push.outputs.update-type }}
PR_TITLE: ${{ github.event.pull_request.title || steps.pr_lookup.outputs.pr_title }}
ACTION: ${{ github.event.action }}
MERGED: ${{ github.event.pull_request.merged }}
GH_EVENT: ${{ github.event_name }}
run: |
EVENT="unknown"
if [[ "${{ github.event.action }}" == "opened" ]]; then
if [[ "${{ steps.meta.outputs.update-type }}" == "version-update:semver-major" ]]; then
EVENT="opened_major"
UPDATE_TYPE="${FM_UPDATE_TYPE}"
if [[ -z "$UPDATE_TYPE" || "$UPDATE_TYPE" == "null" ]]; then
if [[ "$PR_TITLE" =~ ([0-9]+)\.([0-9]+)\.([0-9]+).*to.*([0-9]+)\.([0-9]+)\.([0-9]+) ]]; then
OM="${BASH_REMATCH[1]}"; Om="${BASH_REMATCH[2]}"
NM="${BASH_REMATCH[4]}"; Nm="${BASH_REMATCH[5]}"
if [[ "$OM" != "$NM" ]]; then UPDATE_TYPE="version-update:semver-major"
elif [[ "$Om" != "$Nm" ]]; then UPDATE_TYPE="version-update:semver-minor"
else UPDATE_TYPE="version-update:semver-patch"; fi
else
EVENT="opened_minor_patch"
UPDATE_TYPE="unknown"
fi
elif [[ "${{ github.event.action }}" == "closed" && "${{ github.event.pull_request.merged }}" == "true" ]]; then
fi

EVENT="skip"
if [[ "$GH_EVENT" == "pull_request_target" && "$ACTION" == "opened" ]]; then
if [[ "$UPDATE_TYPE" == "version-update:semver-major" ]]; then EVENT="opened_major"
else EVENT="opened_minor_patch"; fi
elif [[ "$GH_EVENT" == "pull_request" && "$ACTION" == "closed" && "$MERGED" == "true" ]]; then
EVENT="merged"
elif [[ "$GH_EVENT" == "push" ]]; then
EVENT="merged"
else
echo "Not a tracked event. Skipping notification."
echo "event=skip" >> "$GITHUB_OUTPUT"
exit 0
fi

case "$UPDATE_TYPE" in
version-update:semver-patch) UPDATE_LABEL="patch update";;
version-update:semver-minor) UPDATE_LABEL="minor update";;
version-update:semver-major) UPDATE_LABEL="major update (may contain breaking changes)";;
*) UPDATE_LABEL="update";;
esac

echo "event=$EVENT" >> "$GITHUB_OUTPUT"
echo "update-type=$UPDATE_TYPE" >> "$GITHUB_OUTPUT"
echo "update-label=$UPDATE_LABEL" >> "$GITHUB_OUTPUT"

- name: Send Google Chat card
- name: Send Google Chat card (threaded per repo)
if: steps.classify.outputs.event != 'skip'
env:
WEBHOOK_URL: ${{ secrets.GOOGLE_CHAT_WEBHOOK_URL }}
EVENT: ${{ steps.classify.outputs.event }}
UPDATE_TYPE: ${{ steps.meta.outputs.update-type }}
DEP_NAMES: ${{ steps.meta.outputs.dependency-names }}
ECOSYSTEM: ${{ steps.meta.outputs.package-ecosystem }}
DIRECTORY: ${{ steps.meta.outputs.directory }}
PREVIOUS_VERSION: ${{ steps.meta.outputs.previous-version }}
NEW_VERSION: ${{ steps.meta.outputs.new-version }}
CVSS: ${{ steps.meta.outputs.cvss || '0' }}
DEP_GROUP: ${{ steps.meta.outputs.dependency-group }}
PR_URL: ${{ github.event.pull_request.html_url }}
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_TITLE: ${{ github.event.pull_request.title }}
UPDATE_LABEL: ${{ steps.classify.outputs.update-label }}
DEP_NAMES: ${{ steps.meta_pr.outputs.dependency-names || steps.meta_push.outputs.dependency-names }}
ECOSYSTEM: ${{ steps.meta_pr.outputs.package-ecosystem || steps.meta_push.outputs.package-ecosystem }}
DIRECTORY: ${{ steps.meta_pr.outputs.directory || steps.meta_push.outputs.directory }}
PREVIOUS_VERSION: ${{ steps.meta_pr.outputs.previous-version || steps.meta_push.outputs.previous-version }}
NEW_VERSION: ${{ steps.meta_pr.outputs.new-version || steps.meta_push.outputs.new-version }}
DEP_GROUP: ${{ steps.meta_pr.outputs.dependency-group || steps.meta_push.outputs.dependency-group }}
CVSS: ${{ steps.meta_pr.outputs.cvss || steps.meta_push.outputs.cvss || '0' }}
PR_URL: ${{ github.event.pull_request.html_url || steps.pr_lookup.outputs.pr_url }}
PR_NUMBER: ${{ github.event.pull_request.number || steps.pr_lookup.outputs.pr_number }}
PR_TITLE: ${{ github.event.pull_request.title || steps.pr_lookup.outputs.pr_title }}
REPO: ${{ github.repository }}
MERGED_BY: ${{ github.event.pull_request.merged_by.login }}
MERGED_BY: ${{ github.event.pull_request.merged_by.login || github.event.head_commit.author.username || 'app/github-actions' }}
run: |
case "$ECOSYSTEM" in
npm_and_yarn) ECO_LABEL="npm";;
pip) ECO_LABEL="pip";;
github_actions) ECO_LABEL="GitHub Actions";;
docker) ECO_LABEL="Docker";;
*) ECO_LABEL="$ECOSYSTEM";;
esac
ECO_DISPLAY="$ECO_LABEL"
if [[ -n "$DIRECTORY" && "$DIRECTORY" != "/" ]]; then
ECO_DISPLAY="$ECO_LABEL ($DIRECTORY)"
fi
if [[ -n "$PREVIOUS_VERSION" && -n "$NEW_VERSION" ]]; then
VERSION_TEXT="$PREVIOUS_VERSION -> $NEW_VERSION"
else
VERSION_TEXT="see PR for versions"
fi
if [[ -n "$DEP_GROUP" ]]; then
PKG_COUNT=$(echo "$DEP_NAMES" | tr ',' '\n' | wc -l | tr -d ' ')
PKG_SUMMARY="$DEP_GROUP group ($PKG_COUNT packages)"
else
PKG_SUMMARY="$DEP_NAMES"
fi
case "$EVENT" in
opened_minor_patch)
TITLE="Dependabot PR opened — auto-merge enabled"
SUBTITLE="${REPO} · #${PR_NUMBER}"
STATUS_TEXT="Auto-merge enabled. Will merge once CI passes."
STATUS_LABEL="Status"
ACTION_BUTTON_TEXT="View PR"
HEADER_TITLE="Safe update - auto-merging after CI"
STATUS_LABEL="Action Required"
STATUS_TEXT="No action needed. Auto-merge enabled; will merge once CI passes."
BUTTON_TEXT="View PR"
;;
opened_major)
TITLE="MAJOR version bump — human review required"
SUBTITLE="${REPO} · #${PR_NUMBER}"
STATUS_TEXT="This is a MAJOR version upgrade and may contain breaking changes. Please review and merge manually if safe."
HEADER_TITLE="MAJOR version bump - human review required"
STATUS_LABEL="Action Required"
ACTION_BUTTON_TEXT="Review PR"
STATUS_TEXT="This is a MAJOR version upgrade and may contain breaking changes. Please review and merge manually if safe."
BUTTON_TEXT="Review PR"
;;
merged)
TITLE="Dependabot PR merged"
SUBTITLE="${REPO} · #${PR_NUMBER}"
STATUS_TEXT="Merged into default branch${MERGED_BY:+ by ${MERGED_BY}}."
STATUS_LABEL="Status"
ACTION_BUTTON_TEXT="View PR"
HEADER_TITLE="Merged into main"
STATUS_LABEL="Merged By"
if [[ -n "$MERGED_BY" && "$MERGED_BY" != "null" ]]; then
STATUS_TEXT="$MERGED_BY"
else
STATUS_TEXT="dependabot[bot] (auto-merge)"
fi
BUTTON_TEXT="View PR"
;;
esac
SUBTITLE="$REPO - PR #$PR_NUMBER"
CARD_ID="dependabot-$EVENT-$(date +%s)"
THREAD_KEY=$(echo "$REPO" | tr '[:upper:]' '[:lower:]' | tr '/' '-')
THREADED_URL="${WEBHOOK_URL}&threadKey=${THREAD_KEY}&messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD"

VERSION_TEXT="${PREVIOUS_VERSION:-?} → ${NEW_VERSION:-?}"
[[ "$VERSION_TEXT" == "? → ?" ]] && VERSION_TEXT="see PR for versions"

GROUP_LINE=""
if [[ -n "$DEP_GROUP" ]]; then
GROUP_LINE=",{\"decoratedText\": {\"topLabel\": \"Group\", \"text\": \"${DEP_GROUP}\"}}"
fi
jq -n \
--arg cardId "$CARD_ID" \
--arg title "$HEADER_TITLE" \
--arg subtitle "$SUBTITLE" \
--arg statusLabel "$STATUS_LABEL" \
--arg statusText "$STATUS_TEXT" \
--arg prTitle "$PR_TITLE" \
--arg pkgSummary "$PKG_SUMMARY" \
--arg versionText "$VERSION_TEXT" \
--arg updateLabel "$UPDATE_LABEL" \
--arg ecoDisplay "$ECO_DISPLAY" \
--arg buttonText "$BUTTON_TEXT" \
--arg prUrl "$PR_URL" \
--arg cvss "$CVSS" \
'{
cardsV2: [{
cardId: $cardId,
card: {
header: {
title: $title,
subtitle: $subtitle,
imageUrl: "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png",
imageType: "CIRCLE"
},
sections: [{
widgets: (
[
{decoratedText: {topLabel: $statusLabel, text: $statusText, wrapText: true}},
{decoratedText: {topLabel: "PR Title", text: $prTitle, wrapText: true}},
{decoratedText: {topLabel: "Package(s)", text: $pkgSummary, wrapText: true}},
{decoratedText: {topLabel: "Version", text: $versionText}},
{decoratedText: {topLabel: "Update Type", text: $updateLabel}},
{decoratedText: {topLabel: "Ecosystem", text: $ecoDisplay}}
]
+ (if ($cvss | IN("0","0.0","")) then [] else [{decoratedText: {topLabel: "CVSS Score (Common Vulnerability Scoring System, 0 to 10 scale)", text: $cvss}}] end)
+ [{buttonList: {buttons: [{text: $buttonText, onClick: {openLink: {url: $prUrl}}}]}}]
)
}]
}
}]
}' > payload.json

cat > payload.json <<EOF
{
"cardsV2": [{
"cardId": "dependabot-${EVENT}-$(date +%s)",
"card": {
"header": {
"title": "${TITLE}",
"subtitle": "${SUBTITLE}",
"imageUrl": "https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png",
"imageType": "CIRCLE"
},
"sections": [{
"widgets": [
{"decoratedText": {"topLabel": "${STATUS_LABEL}", "text": "${STATUS_TEXT}", "wrapText": true}},
{"decoratedText": {"topLabel": "PR Title", "text": "${PR_TITLE}", "wrapText": true}},
{"decoratedText": {"topLabel": "Package(s)", "text": "${DEP_NAMES}", "wrapText": true}},
{"decoratedText": {"topLabel": "Version", "text": "${VERSION_TEXT}"}},
{"decoratedText": {"topLabel": "Update Type", "text": "${UPDATE_TYPE}"}},
{"decoratedText": {"topLabel": "Ecosystem", "text": "${ECOSYSTEM} (${DIRECTORY})"}}${GROUP_LINE},
{"decoratedText": {"topLabel": "CVSS Score", "text": "${CVSS}"}},
{"buttonList": {"buttons": [
{"text": "${ACTION_BUTTON_TEXT}", "onClick": {"openLink": {"url": "${PR_URL}"}}}
]}}
]
}]
}
}]
}
EOF
curl -sS -X POST "$WEBHOOK_URL" \
curl -sS -X POST "$THREADED_URL" \
-H "Content-Type: application/json; charset=UTF-8" \
-d @payload.json
Loading