-
Notifications
You must be signed in to change notification settings - Fork 520
147 lines (113 loc) · 4.56 KB
/
check_protected.yml
File metadata and controls
147 lines (113 loc) · 4.56 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
name: Check Protected Files
on:
workflow_dispatch: {}
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
pull_request_review:
types: [submitted, edited, dismissed]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Install dependencies
run: |
npm install js-yaml minimatch
- name: Load protected config
uses: actions/github-script@v8
id: config
with:
result-encoding: json
script: |
const fs = require('fs')
const yaml = require('js-yaml')
const configPath = '.github/config/protected_files.yml'
if(!fs.existsSync(configPath)) {
core.setFailed(`Protected config file missing at: ${configPath}. This file is required.`)
return
}
try {
const raw = fs.readFileSync(configPath, 'utf8')
const config = yaml.load(raw)
if (!config || !config.approval_team || !config.protected_files){
core.setFailed(`Protected config file missing or malformed at: ${configPath}. This file is required.`)
return
}
return config
} catch (err) {
core.setFailed(`Failed to load protected config: ${err}`)
}
- name: Get changed files
uses: actions/github-script@v8
id: changed
with:
result-encoding: json
script: |
const { data: files } = await github.rest.pulls.listFiles({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
})
const changed = [].concat(files ?? [])?.flatMap(f => f?.filename)?.filter(Boolean)
return changed
- name: Check protected files and approvals
id: reviewers
uses: actions/github-script@v8
with:
result-encoding: json
script: |
const { minimatch } = require('minimatch')
const changed = ${{ steps.changed.outputs.result }}
const config = ${{ steps.config.outputs.result }}
const protectedFiles = config.protected_files
const approvalTeam = config.approval_team
const affected = changed
?.filter(f =>
protectedFiles.some(pattern => minimatch(f, pattern))
)
if (affected?.length <= 0) {
core.info("No protected files modified!")
return
}
const owner = context.repo.owner
const repo = context.repo.repo
const pull_number = context.issue.number
const pr_author = context.payload.pull_request.user.login
const reviews = await github.paginate(github.rest.pulls.listReviews, {
owner,
repo,
pull_number,
})
const requestedReviews = await github.paginate(github.rest.pulls.listRequestedReviewers, {
owner,
repo,
pull_number
})
const usersApproved = reviews
.filter(r => r.state === 'APPROVED')
.map(r => r?.user?.login)
.filter(Boolean)
const usersRequested = requestedReviews.flatMap(r => r.users.map(u => u.login)).filter(Boolean)
const missingApprovals = approvalTeam.filter(u => !usersApproved.includes(u) && u !== pr_author)
const teamApprovals = approvalTeam.filter(u => usersApproved.includes(u) && u !== pr_author)
const reviewersToRequest = missingApprovals.filter(u => !usersRequested.includes(u))
if (reviewersToRequest?.length > 0) {
await github.rest.pulls.requestReviewers({
owner,
repo,
pull_number,
reviewers: reviewersToRequest
})
}
const isApproved = approvalTeam.some(u => usersApproved.includes(u))
if (isApproved) {
core.info(`Required approvals are present!\nApproved by: ${teamApprovals?.join(', ')}`)
return
} else {
core.setFailed(`Some protected files have been changed but not all required approvals are present. Missing approvals from one of: ${missingApprovals
?.filter(u => !usersApproved?.includes(u))
?.join(', ')}`)
}