Skip to content

Commit 243dd8c

Browse files
authored
Merge pull request #431 from maxmind/wstorey/stf-1245-dependabot-failure-watcher-emails-go-to-the-correct-person
Ignore Dependabot security updates in the failure watcher
2 parents 6affce5 + 8fe7984 commit 243dd8c

1 file changed

Lines changed: 90 additions & 10 deletions

File tree

.github/workflows/dependabot-failure-watcher.yml

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,77 @@ name: Dependabot Failure Watcher
22

33
# Dependabot version updates run as GitHub Actions workflow runs named
44
# "Dependabot Updates". This scheduled job looks back over the past week for any
5-
# of those runs that failed and fails itself if it finds one, so a silently-broken
6-
# ecosystem surfaces as a red scheduled run instead of only a red triangle in the
7-
# Dependabot tab that nobody checks.
5+
# version-update run that failed and fails itself if it finds one, so a
6+
# silently-broken ecosystem surfaces as a red scheduled run instead of only a red
7+
# triangle in the Dependabot tab that nobody checks. Security-update runs share
8+
# that workflow name and are deliberately excluded -- see below.
9+
#
10+
# GitHub reuses the "Dependabot Updates" name for three different kinds of run:
11+
#
12+
# 1. A version update's scheduled scan: one run per .github/dependabot.yml
13+
# entry, on the schedule set there. It works out what is out of date and
14+
# opens or updates pull requests. This is the kind this watcher primarily
15+
# exists to catch -- when a scan breaks, the whole ecosystem quietly stops
16+
# being updated and nothing else tells anyone.
17+
# 2. A version update's per-pull-request refresh: one run per already-open
18+
# Dependabot pull request, rebasing or re-checking it. These are not driven
19+
# by the schedule at all -- a push to the base branch, a rebase, or an
20+
# "@dependabot recreate" comment triggers them, so they arrive in bursts
21+
# after merges rather than at the scheduled time. A failure here means one
22+
# open pull request has gone stale, which is worth knowing but is much
23+
# narrower than a broken scan.
24+
# 3. A security update: one ad-hoc job per vulnerable package, triggered by a
25+
# Dependabot alert rather than by dependabot.yml at all.
26+
#
27+
# Kind 3 routinely fails for reasons no pull request can fix: the advisory is
28+
# against a dependency this project does not declare directly, or no patched
29+
# version is reachable. Counting those would keep this workflow permanently red
30+
# and train everyone to ignore it, so they are filtered out below.
31+
#
32+
# Of the fields "gh run list --json" exposes, only the title separates the three
33+
# -- event, headBranch and actor are identical. Titles come in these shapes:
34+
#
35+
# - "<eco> in /." -- kind 1 at the repo root, which
36+
# has no " for " suffix
37+
# - "<eco> in <configured-dir>" -- kind 1 elsewhere, path verbatim
38+
# - "<eco> in / for <deps>" -- kind 2 at the repo root
39+
# - "<eco> in <configured-dir> for <deps>" -- kind 2 elsewhere
40+
# - "<eco> in /. for <one-dep>" -- kind 3 at the repo root
41+
# - "<eco> in <manifest-dir> for <one-dep>" -- kind 3 elsewhere, where
42+
# <manifest-dir> is wherever the vulnerable manifest was discovered
43+
#
44+
# At the root, then, kind 3 is marked by "/." AND a " for " suffix together, and
45+
# BOTH HALVES of " in /. for " are load-bearing -- do not shorten it. Matching on
46+
# " in /." alone would also discard every kind 1 run, which is most of the runs
47+
# here and the shape both failures this watcher was written for actually took.
48+
#
49+
# Outside the root, kinds 2 and 3 cannot be told apart by title, so the filter
50+
# has to name directories instead. e2e/js and e2e/ts (in the Node repos this
51+
# workflow is shared with) are consumer smoke tests carrying committed
52+
# lockfiles, so their transitive dev dependencies attract advisories that no
53+
# pull request can fix, and nothing in them is shipped code.
54+
#
55+
# Be clear about the cost, because it is not zero: both Node repos configure npm
56+
# with directories: ["/", "**/*"], and that glob does match e2e/js and e2e/ts,
57+
# so those directories DO get version updates. Dropping the pattern therefore
58+
# discards their kind 2 failures as well as their kind 3 ones -- there is an
59+
# open version-update pull request under e2e/ts in both repos as this is
60+
# written. The npm ecosystem label does not rescue the distinction either:
61+
# Dependabot writes "npm_and_yarn" for both kinds, so "npm_and_yarn in /e2e/ts
62+
# for js-yaml" could be either a security job or the refresh of a
63+
# version-update pull request.
64+
#
65+
# Accepted deliberately anyway. Kind 1 is what this watcher primarily exists to
66+
# catch and is still reported for those directories, so what is given up is the
67+
# narrower "one open pull request has gone stale" signal, for two directories of
68+
# test scaffolding, in exchange for dropping 16 unactionable failures in each of
69+
# the two Node repos over retained history.
70+
#
71+
# Reading the directories out of dependabot.yml instead looks more general but is
72+
# worse: entries may use globs (directories: ["**/*"]), which never match a title
73+
# literally, so genuine failures would be dropped without a word. Prefer a
74+
# denylist: when it goes stale it re-introduces noise, which is loud, whereas a
75+
# stale allowlist hides failures, which is silent.
876
#
977
# Runs entirely within this repo (no external service). A failed scheduled run
1078
# emails the person who last edited the cron below. Note: GitHub auto-disables
@@ -22,22 +90,34 @@ jobs:
2290
check-dependabot-runs:
2391
runs-on: ubuntu-latest
2492
steps:
25-
- name: Fail if any Dependabot update failed in the last 8 days
93+
- name: Fail if any Dependabot version update failed in the last 8 days
2694
env:
2795
GH_TOKEN: ${{ github.token }}
2896
REPO: ${{ github.repository }}
2997
run: |
3098
since=$(date -u -d '8 days ago' +%Y-%m-%dT%H:%M:%SZ)
31-
failures=$(gh run list \
99+
# --created filters server-side, so --limit applies to runs already
100+
# narrowed to the window rather than to all of history. Runs come back
101+
# newest-first, so reaching the limit would drop the oldest in-window
102+
# runs and this step would report all-clear without them -- hence a
103+
# limit far above any plausible week's worth of runs.
104+
runs=$(gh run list \
32105
--repo "$REPO" \
33106
--workflow "Dependabot Updates" \
34-
--limit 100 \
35-
--json conclusion,createdAt,displayTitle,url \
36-
--jq "[.[] | select((.conclusion == \"failure\" or .conclusion == \"startup_failure\" or .conclusion == \"timed_out\") and .createdAt >= \"$since\")]")
107+
--created ">=$since" \
108+
--limit 500 \
109+
--json conclusion,createdAt,displayTitle,url)
110+
failures=$(echo "$runs" | jq '
111+
[.[]
112+
| select((.displayTitle | contains(" in /. for ")) | not)
113+
| select((.displayTitle | test(" in /e2e/(js|ts) for ")) | not)
114+
| select(.conclusion == "failure"
115+
or .conclusion == "startup_failure"
116+
or .conclusion == "timed_out")]')
37117
count=$(echo "$failures" | jq 'length')
38118
if [ "$count" -gt 0 ]; then
39-
echo "::error::$count failed Dependabot update run(s) in the last 8 days:"
119+
echo "::error::$count failed Dependabot version update run(s) in the last 8 days:"
40120
echo "$failures" | jq -r '.[] | "- \(.displayTitle) (\(.createdAt))\n \(.url)"'
41121
exit 1
42122
fi
43-
echo "No failed Dependabot update runs in the last 8 days."
123+
echo "No failed Dependabot version update runs in the last 8 days."

0 commit comments

Comments
 (0)