-
Notifications
You must be signed in to change notification settings - Fork 0
155 lines (143 loc) · 6.13 KB
/
sign-modules-on-approval.yml
File metadata and controls
155 lines (143 loc) · 6.13 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
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: sign-modules-on-approval
on:
pull_request_review:
types: [submitted]
concurrency:
group: sign-modules-on-approval-${{ github.event.pull_request.number || github.event.number }}
cancel-in-progress: true
permissions:
contents: write
pull-requests: read
jobs:
sign-modules:
runs-on: ubuntu-latest
env:
SPECFACT_MODULE_PRIVATE_SIGN_KEY: ${{ secrets.SPECFACT_MODULE_PRIVATE_SIGN_KEY }}
SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE: ${{ secrets.SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE }}
PR_BASE_REF: ${{ github.event.pull_request.base.ref || github.base_ref }}
PR_HEAD_REF: ${{ github.event.pull_request.head.ref || github.head_ref }}
steps:
- name: Eligibility gate (required status check)
id: gate
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
review_state="${{ github.event.review.state || '' }}"
author_association="${{ github.event.review.user.author_association || '' }}"
base_ref="${{ github.event.pull_request.base.ref }}"
if [ "$base_ref" != "dev" ] && [ "$base_ref" != "main" ]; then
echo "sign=false" >> "$GITHUB_OUTPUT"
echo "::notice::Skipping module signing: base branch is not dev or main."
exit 0
fi
head_repo="${{ github.event.pull_request.head.repo.full_name }}"
this_repo="${{ github.repository }}"
if [ "$head_repo" != "$this_repo" ]; then
echo "sign=false" >> "$GITHUB_OUTPUT"
echo "::notice::Skipping module signing: fork PR (head repo differs from target repo)."
exit 0
fi
if [ "$review_state" != "approved" ]; then
echo "sign=false" >> "$GITHUB_OUTPUT"
echo "::notice::Skipping module signing: review state is not approved."
exit 0
fi
case "$author_association" in
COLLABORATOR|MEMBER|OWNER)
;;
*)
echo "sign=false" >> "$GITHUB_OUTPUT"
echo "::notice::Skipping module signing: reviewer association '${author_association}' is not trusted for signing."
exit 0
;;
esac
echo "sign=true" >> "$GITHUB_OUTPUT"
echo "Eligible for module signing (same-repo PR to dev or main with trusted approval state)."
- name: Guard signing secrets
if: steps.gate.outputs.sign == 'true'
run: |
set -euo pipefail
if [ -z "${SPECFACT_MODULE_PRIVATE_SIGN_KEY:-}" ]; then
echo "::error::Missing secret: SPECFACT_MODULE_PRIVATE_SIGN_KEY"
exit 1
fi
if [ -z "${SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE:-}" ]; then
echo "::error::Missing secret: SPECFACT_MODULE_PRIVATE_SIGN_KEY_PASSPHRASE"
exit 1
fi
- uses: actions/checkout@v4
if: steps.gate.outputs.sign == 'true'
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Set up Python 3.12
if: steps.gate.outputs.sign == 'true'
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install signing dependencies
if: steps.gate.outputs.sign == 'true'
run: |
python -m pip install --upgrade pip
python -m pip install pyyaml beartype icontract cryptography cffi
- name: Discover module manifests
if: steps.gate.outputs.sign == 'true'
id: discover
run: |
set -euo pipefail
mapfile -t MANIFESTS < <(find packages -name 'module-package.yaml' -type f | sort)
echo "manifests_count=${#MANIFESTS[@]}" >> "$GITHUB_OUTPUT"
echo "Discovered ${#MANIFESTS[@]} module-package.yaml file(s) under packages/"
- name: Sign changed module manifests
if: steps.gate.outputs.sign == 'true'
id: sign
run: |
set -euo pipefail
git fetch origin "${PR_BASE_REF}" --no-tags
MERGE_BASE="$(git merge-base HEAD "origin/${PR_BASE_REF}")"
python scripts/sign-modules.py \
--changed-only \
--base-ref "$MERGE_BASE" \
--bump-version patch \
--payload-from-filesystem
- name: Commit and push signed manifests
if: steps.gate.outputs.sign == 'true'
id: commit
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
if [ -z "$(git status --porcelain -- packages/)" ]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No manifest changes to commit."
exit 0
fi
git add -u -- packages/
git commit -m "chore(modules): ci sign changed modules"
echo "changed=true" >> "$GITHUB_OUTPUT"
if ! git push origin "HEAD:${PR_HEAD_REF}"; then
echo "::error::Push to ${PR_HEAD_REF} failed (branch may have advanced after the approved commit). Update the PR branch and re-approve if signing is still required."
exit 1
fi
- name: Write job summary
if: always()
env:
GATE_SIGN: ${{ steps.gate.outputs.sign }}
COMMIT_CHANGED: ${{ steps.commit.outputs.changed || '' }}
MANIFESTS_COUNT: ${{ steps.discover.outputs.manifests_count || '' }}
run: |
{
echo "### Module signing (CI approval)"
if [ "${GATE_SIGN}" != "true" ]; then
echo "Signing skipped (eligibility gate: no trusted approval, wrong base branch, or fork PR)."
else
echo "Manifests discovered under \`packages/\`: ${MANIFESTS_COUNT:-unknown}"
if [ "${COMMIT_CHANGED}" = "true" ]; then
echo "Committed signed manifest updates to ${PR_HEAD_REF}."
else
echo "No changes detected (manifests already signed or no module changes on this PR vs merge-base)."
fi
fi
} >> "$GITHUB_STEP_SUMMARY"