-
Notifications
You must be signed in to change notification settings - Fork 957
188 lines (159 loc) · 6.33 KB
/
Copy pathdependency-auto-merge.yml
File metadata and controls
188 lines (159 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
name: Dependency Bot Auto-Merge
on:
pull_request_target:
types: [ opened, reopened, synchronize, ready_for_review ]
workflow_run:
workflows:
- BTrace CI/CD
- CodeQL
types: [ completed ]
permissions:
actions: read
checks: read
contents: write
pull-requests: write
statuses: read
defaults:
run:
shell: bash
env:
REQUIRED_CI_WORKFLOWS: |
BTrace CI/CD
CodeQL
jobs:
approve:
name: Approve dependency bot PR
if: >
github.event_name == 'pull_request_target' &&
!github.event.pull_request.draft &&
contains(fromJSON('["dependabot[bot]","renovate[bot]"]'), github.event.pull_request.user.login)
runs-on: ubuntu-latest
steps:
- name: Approve PR
env:
GH_TOKEN: ${{ secrets.DEPENDENCY_AUTOMERGE_TOKEN || github.token }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
set -euo pipefail
gh pr review "${PR_URL}" \
--approve \
--body "Auto-approved dependency bot PR." || \
echo "::notice::Approval was skipped; it may already be approved."
merge:
name: Merge dependency bot PR
if: >
github.event_name == 'workflow_run' &&
github.event.workflow_run.event == 'pull_request' &&
github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- name: Merge clean dependency bot PRs
env:
GH_TOKEN: ${{ secrets.DEPENDENCY_AUTOMERGE_TOKEN || github.token }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
PULL_REQUESTS: ${{ toJson(github.event.workflow_run.pull_requests) }}
REPOSITORY: ${{ github.repository }}
run: |
set -euo pipefail
is_dependency_bot() {
case "$1" in
dependabot\[bot\]|renovate\[bot\]) return 0 ;;
*) return 1 ;;
esac
}
required_workflows_passed() {
local sha="$1"
local runs_json workflow status conclusion
runs_json="$(gh api "repos/${REPOSITORY}/actions/runs" \
--method GET \
-f event=pull_request \
-f head_sha="${sha}" \
-F per_page=100)"
while IFS= read -r workflow; do
[[ -z "${workflow}" ]] && continue
status="$(jq -r --arg workflow "${workflow}" '
[.workflow_runs[] | select(.name == $workflow)]
| sort_by(.created_at)
| last
| .status // ""
' <<< "${runs_json}")"
conclusion="$(jq -r --arg workflow "${workflow}" '
[.workflow_runs[] | select(.name == $workflow)]
| sort_by(.created_at)
| last
| .conclusion // ""
' <<< "${runs_json}")"
if [[ "${status}" != "completed" || "${conclusion}" != "success" ]]; then
echo "::notice::Waiting for ${workflow} on ${sha} (status=${status:-missing}, conclusion=${conclusion:-missing})."
return 1
fi
done <<< "${REQUIRED_CI_WORKFLOWS}"
}
clean_commit_checks() {
local sha="$1"
local checks_json statuses_json bad_checks bad_statuses
checks_json="$(gh api "repos/${REPOSITORY}/commits/${sha}/check-runs" \
--method GET \
-f filter=latest \
-F per_page=100)"
bad_checks="$(jq -r '
.check_runs[]
| select(
(.name == "Approve dependency bot PR" or .name == "Merge dependency bot PR") | not
)
| select(
.status != "completed" or
((.conclusion == "success" or .conclusion == "skipped" or .conclusion == "neutral") | not)
)
| "- \(.name): \(.status)/\(.conclusion // "none")"
' <<< "${checks_json}")"
statuses_json="$(gh api "repos/${REPOSITORY}/commits/${sha}/status")"
bad_statuses="$(jq -r '
.statuses[]
| select((.context | contains("Dependency Bot Auto-Merge")) | not)
| select(.state != "success")
| "- \(.context): \(.state)"
' <<< "${statuses_json}")"
if [[ -n "${bad_checks}" || -n "${bad_statuses}" ]]; then
echo "::notice::CI is not clean for ${sha}."
[[ -n "${bad_checks}" ]] && printf '%s\n' "${bad_checks}"
[[ -n "${bad_statuses}" ]] && printf '%s\n' "${bad_statuses}"
return 1
fi
}
mapfile -t pr_numbers < <(jq -r '.[].number' <<< "${PULL_REQUESTS}")
if [[ "${#pr_numbers[@]}" -eq 0 ]]; then
echo "::notice::No pull requests were attached to this workflow run."
exit 0
fi
for pr_number in "${pr_numbers[@]}"; do
pr_json="$(gh api "repos/${REPOSITORY}/pulls/${pr_number}")"
author="$(jq -r '.user.login' <<< "${pr_json}")"
draft="$(jq -r '.draft' <<< "${pr_json}")"
head_sha="$(jq -r '.head.sha' <<< "${pr_json}")"
pr_state="$(jq -r '.state' <<< "${pr_json}")"
pr_url="$(jq -r '.html_url' <<< "${pr_json}")"
if ! is_dependency_bot "${author}"; then
echo "::notice::Skipping PR #${pr_number}: author ${author} is not a dependency bot."
continue
fi
if [[ "${pr_state}" != "open" || "${draft}" == "true" ]]; then
echo "::notice::Skipping PR #${pr_number}: state=${pr_state}, draft=${draft}."
continue
fi
if [[ "${head_sha}" != "${HEAD_SHA}" ]]; then
echo "::notice::Skipping PR #${pr_number}: workflow ran on ${HEAD_SHA}, current head is ${head_sha}."
continue
fi
if ! required_workflows_passed "${head_sha}"; then
continue
fi
if ! clean_commit_checks "${head_sha}"; then
continue
fi
gh pr review "${pr_url}" \
--approve \
--body "Auto-approved dependency bot PR after CI passed." || \
echo "::notice::Approval for PR #${pr_number} was skipped; it may already be approved."
gh pr merge "${pr_url}" --squash --delete-branch
done