-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathaction.yml
More file actions
244 lines (230 loc) · 10.4 KB
/
action.yml
File metadata and controls
244 lines (230 loc) · 10.4 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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
# =============================================================================
# Drift-check composite action
# =============================================================================
#
# Reusable action invoked by:
# - the meta-repo's own drift-check.yml workflow (mode: all)
# - tool-repo validate.yml workflows (mode: self) — wired up in Session D
#
# What it does:
# 1. Checks out THIS action's repo (TMHSDigital/Developer-Tools-Directory),
# which is where the drift_check Python package lives. Tool repos that
# consume this action only need to reference it by tag; they do NOT
# need to vendor the checker.
# 2. Sets up Python and installs runtime deps (drift_check is stdlib-only
# today, but pip install -r requirements.txt runs anyway in case it
# grows deps in Phase 3).
# 3. Runs the checker in `self` mode (caller's checkout) or `all` mode
# (every active repo in registry.json via sparse-checkout).
# 4. Optionally upserts the sticky drift issue (meta-repo only).
# 5. Writes a step summary and exposes counts as outputs so the calling
# workflow can branch on them.
#
# Pinning rule: tool repos MUST consume this action via @v1.7 (or a SHA),
# never @main. The drift checker's release pipeline maintains the v1.7 tag
# pointing at the latest 1.7.x. Q9 of the design doc explains why @main is
# forbidden for tool-repo callers (would break every tool-repo PR on
# unrelated meta-repo changes).
# =============================================================================
name: 'Agent-file drift check'
description: 'Run the drift checker in self or all mode and optionally upsert the sticky issue.'
author: 'TMHSDigital'
inputs:
mode:
description: '"self" to check only the calling repo (uses GITHUB_WORKSPACE); "all" to check every active registry.json entry via sparse-checkout.'
required: true
default: 'self'
format:
description: 'Output format: markdown | json | gh-summary. Defaults to gh-summary so step summary is populated.'
required: false
default: 'gh-summary'
github-token:
description: 'GitHub token for cross-repo sparse-checkout in mode=all. Optional for mode=self. Typically a fine-grained PAT (DRIFT_CHECK_TOKEN) with Contents:Read on every registry repo.'
required: false
default: ''
issues-token:
description: 'GitHub token used for sticky-issue upsert. Defaults to github-token. The meta-repo workflow should pass GITHUB_TOKEN here (with issues:write in the workflow permissions block) since fine-grained PATs may lack GraphQL updateIssue access.'
required: false
default: ''
update-sticky-issue:
description: 'true to upsert the sticky drift report issue. Only set true in the meta-repo workflow.'
required: false
default: 'false'
python-version:
description: 'Python interpreter for the checker.'
required: false
default: '3.11'
meta-repo-ref:
description: 'git ref of TMHSDigital/Developer-Tools-Directory to use for the checker code. Defaults to v1 (auto-maintained MAJOR floating tag, per DTD#14). Future MINORs auto-flow through this default without requiring action.yml edits.'
required: false
default: 'v1'
caller-path:
description: 'When mode=self, path inside GITHUB_WORKSPACE that points at the caller checkout. Defaults to "." (root).'
required: false
default: '.'
outputs:
exit-code:
description: 'drift checker exit code: 0=clean, 1=drift, 2=tool error'
value: ${{ steps.run.outputs.exit-code }}
error-count:
description: 'number of error-severity findings'
value: ${{ steps.run.outputs.error-count }}
warning-count:
description: 'number of warning-severity findings'
value: ${{ steps.run.outputs.warning-count }}
sticky-issue-action:
description: 'one of: created, updated, reopened, closed, no_op, skipped'
value: ${{ steps.sticky.outputs.action }}
runs:
using: 'composite'
steps:
# The drift checker code lives in this repo. Whether the caller is the
# meta-repo itself or a tool repo, we always need the checker code at
# a known path. Use a dedicated subdirectory so we don't clobber the
# caller's own checkout (which lives at GITHUB_WORKSPACE).
- name: Checkout drift checker
uses: actions/checkout@v5
with:
repository: TMHSDigital/Developer-Tools-Directory
ref: ${{ inputs.meta-repo-ref }}
path: .drift-checker
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ inputs.python-version }}
- name: Install dependencies (best-effort)
shell: bash
working-directory: .drift-checker
run: |
if [ -f requirements.txt ]; then
pip install -r requirements.txt
else
echo "no requirements.txt — drift_check is stdlib-only, skipping"
fi
- name: Resolve meta-commit
id: meta
shell: bash
working-directory: .drift-checker
run: |
echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT"
- name: Run drift checker
id: run
shell: bash
working-directory: .drift-checker
env:
DRIFT_CHECK_TOKEN: ${{ inputs.github-token }}
run: |
set +e
EXTRA_ARGS=()
if [ "${{ inputs.mode }}" = "all" ]; then
EXTRA_ARGS+=(--all)
elif [ "${{ inputs.mode }}" = "self" ]; then
EXTRA_ARGS+=(--local "$GITHUB_WORKSPACE/${{ inputs.caller-path }}")
else
echo "::error::invalid mode: ${{ inputs.mode }} (expected 'self' or 'all')"
exit 2
fi
# Always emit JSON to a side-channel file so we can extract counts
# for outputs, then write the requested format separately.
python scripts/drift_check/cli.py \
"${EXTRA_ARGS[@]}" \
--format json \
--output /tmp/drift-findings.json \
--meta-commit "${{ steps.meta.outputs.sha }}"
FINDINGS_RC=$?
# Extract counts (default to 0 if jq missing or file absent).
ERR=0; WARN=0
if command -v jq >/dev/null 2>&1 && [ -f /tmp/drift-findings.json ]; then
ERR=$(jq -r '.summary.errors // 0' /tmp/drift-findings.json)
WARN=$(jq -r '.summary.warnings // 0' /tmp/drift-findings.json)
fi
echo "exit-code=$FINDINGS_RC" >> "$GITHUB_OUTPUT"
echo "error-count=$ERR" >> "$GITHUB_OUTPUT"
echo "warning-count=$WARN" >> "$GITHUB_OUTPUT"
# Now render the user-requested format (which writes to step summary
# automatically when format=gh-summary).
if [ "${{ inputs.format }}" != "json" ]; then
python scripts/drift_check/cli.py \
"${EXTRA_ARGS[@]}" \
--format "${{ inputs.format }}" \
--meta-commit "${{ steps.meta.outputs.sha }}"
fi
# Tool errors (rc=2) abort immediately and skip the sticky-issue
# step — there's nothing meaningful to publish if the checker
# itself failed. Drift findings (rc=1) are NOT propagated here so
# the optional sticky-issue step still runs; the final
# "Propagate checker exit code" step at the end of this composite
# surfaces rc=1 to the caller, failing the calling job on drift.
if [ "$FINDINGS_RC" = "2" ]; then
exit 2
fi
exit 0
- name: Upsert sticky issue
id: sticky
if: ${{ inputs.update-sticky-issue == 'true' }}
shell: bash
working-directory: .drift-checker
env:
# For sticky-issue ops we need a token with issues:write GraphQL
# access on the meta-repo. Fine-grained PATs sometimes lack
# updateIssue; the workflow's auto-GITHUB_TOKEN with the
# permissions block is the reliable source. issues-token defaults
# to github-token for backward compatibility.
DRIFT_CHECK_TOKEN: ${{ inputs.github-token }}
GH_TOKEN: ${{ inputs.issues-token != '' && inputs.issues-token || inputs.github-token }}
run: |
set +e
ISSUES_TOKEN="${{ inputs.issues-token != '' && inputs.issues-token || inputs.github-token }}"
if [ -z "$ISSUES_TOKEN" ]; then
echo "::warning::update-sticky-issue=true but no issues-token / github-token provided; skipping"
echo "action=skipped" >> "$GITHUB_OUTPUT"
exit 0
fi
EXTRA_ARGS=()
if [ "${{ inputs.mode }}" = "all" ]; then
EXTRA_ARGS+=(--all)
else
EXTRA_ARGS+=(--local "$GITHUB_WORKSPACE/${{ inputs.caller-path }}")
fi
OUT=$(python scripts/drift_check/cli.py \
"${EXTRA_ARGS[@]}" \
--format json \
--output /dev/null \
--update-sticky-issue \
--meta-commit "${{ steps.meta.outputs.sha }}" 2>&1)
RC=$?
echo "$OUT"
# Parse the "Sticky issue: <action>" line emitted by cli.py.
ACTION=$(echo "$OUT" | grep -oE 'Sticky issue: (created|updated|reopened|closed|no_op)' | head -1 | awk '{print $3}')
echo "action=${ACTION:-unknown}" >> "$GITHUB_OUTPUT"
# Sticky-step rc is best-effort: don't gate the action on a
# transient gh-API hiccup. The final "Propagate checker exit
# code" step uses the original drift-checker rc captured by the
# run step, which is the authoritative pass/fail signal.
exit 0
- name: Propagate checker exit code
# Always run so the action's exit code mirrors the drift checker's
# actual rc, regardless of whether the optional sticky step ran or
# what its rc was. This is what turns the composite into a real
# gate: rc=1 (drift) makes the calling job fail. rc=0 (clean) lets
# it pass. rc=2 (tool error) was already surfaced by the run step
# exiting 2; we mirror it here for completeness. If the run step
# never executed (e.g., checkout failed earlier), exit 0 so we
# don't mask the earlier failure that already failed the action.
if: always()
shell: bash
run: |
RC="${{ steps.run.outputs.exit-code }}"
if [ -z "$RC" ]; then
echo "drift checker did not run; nothing to propagate"
exit 0
fi
echo "drift checker exit code: $RC"
case "$RC" in
0) echo "no drift detected" ;;
1) echo "::error::drift detected — failing job per composite-action contract" ;;
2) echo "::error::tool error in drift checker" ;;
*) echo "::warning::unexpected exit code from drift checker: $RC" ;;
esac
exit "$RC"