Skip to content

Commit 4037b89

Browse files
AmberArcadiaclaude
andcommitted
Add check-pipeline-name-only-steps hook
This hook validates that pipeline steps in melange YAML files don't have only a 'name' field without 'uses' or other details. This catches a common mistake where 'name' is used instead of 'uses' for pipeline steps. The check covers: - Main pipeline steps - Test pipeline steps - Subpackage pipeline steps - Subpackage test pipeline steps Includes test data files demonstrating both valid and invalid configurations. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 626b499 commit 4037b89

6 files changed

Lines changed: 230 additions & 0 deletions

File tree

.pre-commit-hooks.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,13 @@
2020
- manual
2121
types:
2222
- yaml
23+
- id: check-pipeline-name-only-steps
24+
name: check pipeline name-only steps
25+
description: check that pipeline steps don't have only a name without uses or other details
26+
entry: check-pipeline-name-only-steps
27+
language: python
28+
stages:
29+
- pre-commit
30+
- manual
31+
types:
32+
- yaml
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from __future__ import annotations
2+
3+
import argparse
4+
import sys
5+
from collections.abc import Sequence
6+
from typing import Any
7+
8+
import ruamel.yaml
9+
10+
yaml = ruamel.yaml.YAML(typ="safe")
11+
12+
13+
def check_pipeline_steps(melange_cfg: dict[str, Any]) -> tuple[bool, list[str]]:
14+
"""
15+
Check if any pipeline steps have only a 'name' field without 'uses' or other details.
16+
Returns (is_valid, list_of_issues).
17+
"""
18+
issues = []
19+
20+
# Check main pipeline
21+
pipelines = melange_cfg.get("pipeline", [])
22+
for i, step in enumerate(pipelines):
23+
if isinstance(step, dict):
24+
# Check if step has only 'name' and no 'uses'
25+
if "name" in step and "uses" not in step and len(step) == 1:
26+
step_name = step.get("name", f"step {i}")
27+
issues.append(
28+
f"main pipeline step '{step_name}' has only a name with no 'uses' or other details",
29+
)
30+
31+
# Check test pipeline
32+
test_section = melange_cfg.get("test", {})
33+
test_pipelines = test_section.get("pipeline", [])
34+
for i, step in enumerate(test_pipelines):
35+
if isinstance(step, dict):
36+
# Check if step has only 'name' and no 'uses'
37+
if "name" in step and "uses" not in step and len(step) == 1:
38+
step_name = step.get("name", f"step {i}")
39+
issues.append(
40+
f"test pipeline step '{step_name}' has only a name with no 'uses' or other details",
41+
)
42+
43+
# Check each subpackage
44+
for sub_idx, subpkg in enumerate(melange_cfg.get("subpackages", [])):
45+
subpkg_name = subpkg.get("name", f"subpackage-{sub_idx}")
46+
47+
# Check subpackage pipelines
48+
subpkg_pipelines = subpkg.get("pipeline", [])
49+
for i, step in enumerate(subpkg_pipelines):
50+
if isinstance(step, dict):
51+
# Check if step has only 'name' and no 'uses'
52+
if "name" in step and "uses" not in step and len(step) == 1:
53+
step_name = step.get("name", f"step {i}")
54+
issues.append(
55+
f"subpackage '{subpkg_name}' pipeline step '{step_name}' has only a name with no 'uses' or other details",
56+
)
57+
58+
# Check subpackage test pipelines
59+
subpkg_test_section = subpkg.get("test", {})
60+
subpkg_test_pipelines = subpkg_test_section.get("pipeline", [])
61+
for i, step in enumerate(subpkg_test_pipelines):
62+
if isinstance(step, dict):
63+
# Check if step has only 'name' and no 'uses'
64+
if "name" in step and "uses" not in step and len(step) == 1:
65+
step_name = step.get("name", f"step {i}")
66+
issues.append(
67+
f"subpackage '{subpkg_name}' test pipeline step '{step_name}' has only a name with no 'uses' or other details",
68+
)
69+
70+
return len(issues) == 0, issues
71+
72+
73+
def main(argv: Sequence[str] | None = None) -> int:
74+
parser = argparse.ArgumentParser(
75+
description="Check that pipeline steps don't have only a name without uses or other details",
76+
)
77+
parser.add_argument("filenames", nargs="*", help="Filenames to check")
78+
args = parser.parse_args(argv)
79+
80+
retval = 0
81+
82+
for filename in args.filenames:
83+
try:
84+
with open(filename) as f:
85+
melange_cfg = yaml.load(f)
86+
except Exception as e:
87+
print(f"Error loading {filename}: {e}")
88+
retval = 1
89+
continue
90+
91+
if not melange_cfg:
92+
continue
93+
94+
is_valid, issues = check_pipeline_steps(melange_cfg)
95+
if not is_valid:
96+
for issue in issues:
97+
print(f"{filename}: {issue}")
98+
retval = 1
99+
100+
return retval
101+
102+
103+
if __name__ == "__main__":
104+
sys.exit(main())

setup.cfg

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ python_requires = >=3.9
2222
[options.entry_points]
2323
console_scripts =
2424
shellcheck-run-steps = pre_commit_hooks.shellcheck_run_steps:main
25+
check-pipeline-name-only-steps = pre_commit_hooks.check_pipeline_name_only_steps:main
2526

2627
[bdist_wheel]
2728
universal = True

test-data/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Test Data for Pre-commit Hooks
2+
3+
This directory contains sample YAML files that can be used to test the pre-commit hooks in this repository.
4+
5+
## Testing hooks locally
6+
7+
To test a specific hook against a test file, use the `pre-commit try-repo` command:
8+
9+
```bash
10+
# From any directory with YAML files to test:
11+
pre-commit try-repo /path/to/this/repo HOOK_ID --files FILE_TO_TEST
12+
13+
# Example for check-pipeline-name-only-steps:
14+
pre-commit try-repo /home/amber-arcadia/Documents/GitRepos/pre-commit-hooks \
15+
check-pipeline-name-only-steps \
16+
--files test-data/pipeline-name-only-bad.yaml
17+
```
18+
19+
## Test files
20+
21+
### pipeline-name-only-bad.yaml
22+
- **Tests**: `check-pipeline-name-only-steps`
23+
- **Expected**: Should FAIL
24+
- **Issues**: Contains pipeline steps that have only a `name` field without `uses` or other details
25+
26+
### pipeline-name-only-good.yaml
27+
- **Tests**: `check-pipeline-name-only-steps`
28+
- **Expected**: Should PASS
29+
- **Issues**: None - all pipeline steps are properly formatted
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package:
2+
name: test-package
3+
version: "1.0.0"
4+
epoch: 0
5+
description: "Test package with incorrectly formatted pipeline steps"
6+
copyright:
7+
- license: Apache-2.0
8+
9+
environment:
10+
contents:
11+
packages:
12+
- busybox
13+
14+
pipeline:
15+
- name: test/go-fips-check # BAD: This should be 'uses' not 'name'
16+
- name: "Configure build"
17+
uses: autoconf/configure
18+
with:
19+
opts: --enable-shared
20+
- uses: autoconf/make
21+
22+
test:
23+
pipeline:
24+
- name: run-tests # BAD: Only has name, no uses
25+
- uses: test/daemon-check-output
26+
with:
27+
expected_output: |
28+
Server started
29+
30+
subpackages:
31+
- name: test-subpkg
32+
description: "Subpackage with bad pipeline"
33+
pipeline:
34+
- name: bad-subpkg-step # BAD: Only has name
35+
- uses: split/dev
36+
test:
37+
pipeline:
38+
- name: subpkg-test-step # BAD: Only has name in test section
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package:
2+
name: test-package-good
3+
version: "1.0.0"
4+
epoch: 0
5+
description: "Test package with correctly formatted pipeline steps"
6+
copyright:
7+
- license: Apache-2.0
8+
9+
environment:
10+
contents:
11+
packages:
12+
- busybox
13+
- go-fips-1.22
14+
15+
pipeline:
16+
- uses: test/go-fips-check # GOOD: Uses 'uses' instead of 'name'
17+
- name: "Configure build"
18+
uses: autoconf/configure # GOOD: Has both name and uses
19+
with:
20+
opts: --enable-shared
21+
- uses: autoconf/make # GOOD: Just uses is fine
22+
- name: "Run custom script" # GOOD: Has name and runs
23+
runs: |
24+
echo "Building package"
25+
make install
26+
27+
test:
28+
pipeline:
29+
- uses: test/go-fips-check # GOOD: Properly uses 'uses'
30+
- name: "Test daemon output"
31+
uses: test/daemon-check-output # GOOD: Has both name and uses
32+
with:
33+
expected_output: |
34+
Server started
35+
- uses: test/emptypackage # GOOD: Just uses
36+
37+
subpackages:
38+
- name: test-subpkg-good
39+
description: "Subpackage with correct pipeline"
40+
pipeline:
41+
- uses: split/dev # GOOD: Uses 'uses'
42+
- name: "Move files"
43+
runs: | # GOOD: Has name and runs
44+
mkdir -p ${{targets.subpkgdir}}/usr/bin
45+
mv usr/bin/tool ${{targets.subpkgdir}}/usr/bin/
46+
test:
47+
pipeline:
48+
- uses: test/emptypackage # GOOD: Uses 'uses' in test section

0 commit comments

Comments
 (0)