Skip to content

Commit 7ee51cf

Browse files
abaysclaude
andcommitted
Add CI validation for hook ordering prefixes
The ci-framework's run_hook module sorts hooks alphabetically by name before executing them. When a pre_stage_run or post_stage_run stage has two or more hooks, this alphabetical sorting can silently alter the intended execution order unless each hook name carries a zero-padded numeric prefix (e.g. "01 Install operator", "02 Create site"). Add .ci/validate-hook-ordering.py to enforce that: - Every hook in a multi-hook stage has a numeric prefix. - Prefixes are sequential with no gaps or duplicates. Wire the script into the GitHub Actions workflow (.github/workflows/automation-schema.yaml) by renaming the files_exist job to validate_automation and adding a step to run the new validator. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 644a3a3 commit 7ee51cf

2 files changed

Lines changed: 106 additions & 1 deletion

File tree

.ci/validate-hook-ordering.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env python3
2+
"""Validate numeric prefix ordering on pre/post stage hooks.
3+
4+
When a stage has multiple hooks, run_hook sorts them alphabetically by name.
5+
To guarantee execution order, each hook name must start with a zero-padded
6+
numeric prefix (e.g. "01 Install operator", "02 Create site").
7+
8+
This script checks:
9+
- If a stage has >1 hook, every hook name starts with a numeric prefix.
10+
- Prefixes are sequential (01, 02, 03, ...) with no gaps or duplicates.
11+
- If a stage has exactly 1 hook, a prefix is allowed but not required.
12+
"""
13+
14+
import pathlib
15+
import re
16+
import sys
17+
18+
import yaml
19+
20+
PREFIX_RE = re.compile(r'^(\d+)\s+')
21+
22+
def validate_hooks(hooks, stage_label):
23+
"""Validate a list of hooks. Returns list of error strings."""
24+
errors = []
25+
if not hooks or len(hooks) < 2:
26+
return errors
27+
28+
prefixes = []
29+
for hook in hooks:
30+
name = hook.get('name', '<unnamed>')
31+
m = PREFIX_RE.match(name)
32+
if not m:
33+
errors.append(
34+
f'{stage_label}: hook "{name}" is missing a numeric prefix '
35+
f'(required when stage has {len(hooks)} hooks)'
36+
)
37+
else:
38+
prefixes.append((int(m.group(1)), name))
39+
40+
if errors:
41+
return errors
42+
43+
prefixes.sort(key=lambda x: x[0])
44+
seen = set()
45+
for num, name in prefixes:
46+
if num in seen:
47+
errors.append(
48+
f'{stage_label}: duplicate prefix {num:02d} on hook "{name}"'
49+
)
50+
seen.add(num)
51+
52+
nums = [p[0] for p in prefixes]
53+
expected = list(range(nums[0], nums[0] + len(nums)))
54+
if nums != expected:
55+
errors.append(
56+
f'{stage_label}: prefixes {nums} are not sequential '
57+
f'(expected {expected})'
58+
)
59+
60+
return errors
61+
62+
63+
def validate_file(path):
64+
"""Validate all stages in one automation vars file."""
65+
errors = []
66+
with open(path) as fh:
67+
content = yaml.safe_load(fh)
68+
69+
for scenario_name, scenario in content.get('vas', {}).items():
70+
for i, stage in enumerate(scenario.get('stages', [])):
71+
stage_name = stage.get('name', f'stage-{i}')
72+
label = f'{path.name} > {scenario_name} > {stage_name}'
73+
74+
for hook_type in ('pre_stage_run', 'post_stage_run'):
75+
hooks = stage.get(hook_type)
76+
if hooks:
77+
errs = validate_hooks(hooks, f'{label} > {hook_type}')
78+
errors.extend(errs)
79+
80+
return errors
81+
82+
83+
def main():
84+
src_dir = pathlib.Path(__file__).parent / '..' / 'automation' / 'vars'
85+
all_errors = []
86+
87+
for f in sorted(src_dir.glob('*.yaml')):
88+
all_errors.extend(validate_file(f))
89+
90+
if all_errors:
91+
print('Hook ordering errors found:\n')
92+
for err in all_errors:
93+
print(f' ERROR: {err}')
94+
print(f'\n{len(all_errors)} error(s) found.')
95+
sys.exit(1)
96+
else:
97+
print('All hook orderings are valid.')
98+
sys.exit(0)
99+
100+
101+
if __name__ == '__main__':
102+
main()

.github/workflows/automation-schema.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
- name: Run yamale
2929
run: yamale -s .ci/automation-schema.yaml automation/vars/
3030

31-
files_exist:
31+
validate_automation:
3232
runs-on: ubuntu-latest
3333
needs: # Ensure schema is valid before reading it
3434
- yamale
@@ -46,3 +46,6 @@ jobs:
4646

4747
- name: Run file checker
4848
run: python3 .ci/validate-schema-paths.py
49+
50+
- name: Validate hook ordering
51+
run: python3 .ci/validate-hook-ordering.py

0 commit comments

Comments
 (0)