Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@
- manual
types:
- yaml
- id: check-pipeline-name-only-steps
name: check pipeline name-only steps
description: check that pipeline steps don't have only a name without uses or other details
entry: check-pipeline-name-only-steps
language: python
stages:
- pre-commit
- manual
types:
- yaml
104 changes: 104 additions & 0 deletions pre_commit_hooks/check_pipeline_name_only_steps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from __future__ import annotations

import argparse
import sys
from collections.abc import Sequence
from typing import Any

import ruamel.yaml

yaml = ruamel.yaml.YAML(typ="safe")


def check_pipeline_steps(melange_cfg: dict[str, Any]) -> tuple[bool, list[str]]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

melange requires that the main package has a pipeline, so I have created packages with name-only steps. See cuda-stub-meta.yaml in extra-packages as an example. That's likely a melange bug of course, but it's reality today.

"""
Check if any pipeline steps have only a 'name' field without 'uses' or other details.
Returns (is_valid, list_of_issues).
"""
issues = []

# Check main pipeline
pipelines = melange_cfg.get("pipeline", [])
for i, step in enumerate(pipelines):
if isinstance(step, dict):
# Check if step has only 'name' and no 'uses'
if "name" in step and "uses" not in step and len(step) == 1:
step_name = step.get("name", f"step {i}")
issues.append(
f"main pipeline step '{step_name}' has only a name with no 'uses' or other details",
)

# Check test pipeline
test_section = melange_cfg.get("test", {})
test_pipelines = test_section.get("pipeline", [])
for i, step in enumerate(test_pipelines):
if isinstance(step, dict):
# Check if step has only 'name' and no 'uses'
if "name" in step and "uses" not in step and len(step) == 1:
step_name = step.get("name", f"step {i}")
issues.append(
f"test pipeline step '{step_name}' has only a name with no 'uses' or other details",
)

# Check each subpackage
for sub_idx, subpkg in enumerate(melange_cfg.get("subpackages", [])):
subpkg_name = subpkg.get("name", f"subpackage-{sub_idx}")

# Check subpackage pipelines
subpkg_pipelines = subpkg.get("pipeline", [])
for i, step in enumerate(subpkg_pipelines):
if isinstance(step, dict):
# Check if step has only 'name' and no 'uses'
if "name" in step and "uses" not in step and len(step) == 1:
step_name = step.get("name", f"step {i}")
issues.append(
f"subpackage '{subpkg_name}' pipeline step '{step_name}' has only a name with no 'uses' or other details",
)

# Check subpackage test pipelines
subpkg_test_section = subpkg.get("test", {})
subpkg_test_pipelines = subpkg_test_section.get("pipeline", [])
for i, step in enumerate(subpkg_test_pipelines):
if isinstance(step, dict):
# Check if step has only 'name' and no 'uses'
if "name" in step and "uses" not in step and len(step) == 1:
step_name = step.get("name", f"step {i}")
issues.append(
f"subpackage '{subpkg_name}' test pipeline step '{step_name}' has only a name with no 'uses' or other details",
)

return len(issues) == 0, issues


def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Check that pipeline steps don't have only a name without uses or other details",
)
parser.add_argument("filenames", nargs="*", help="Filenames to check")
args = parser.parse_args(argv)

retval = 0

for filename in args.filenames:
try:
with open(filename) as f:
melange_cfg = yaml.load(f)
except Exception as e:
print(f"Error loading {filename}: {e}")
retval = 1
continue

if not melange_cfg:
continue

is_valid, issues = check_pipeline_steps(melange_cfg)
if not is_valid:
for issue in issues:
print(f"{filename}: {issue}")
retval = 1

return retval


if __name__ == "__main__":
sys.exit(main())
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ python_requires = >=3.9
[options.entry_points]
console_scripts =
shellcheck-run-steps = pre_commit_hooks.shellcheck_run_steps:main
check-pipeline-name-only-steps = pre_commit_hooks.check_pipeline_name_only_steps:main

[bdist_wheel]
universal = True
Expand Down
29 changes: 29 additions & 0 deletions test-data/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Test Data for Pre-commit Hooks

This directory contains sample YAML files that can be used to test the pre-commit hooks in this repository.

## Testing hooks locally

To test a specific hook against a test file, use the `pre-commit try-repo` command:

```bash
# From any directory with YAML files to test:
pre-commit try-repo /path/to/this/repo HOOK_ID --files FILE_TO_TEST

# Example for check-pipeline-name-only-steps:
pre-commit try-repo /home/amber-arcadia/Documents/GitRepos/pre-commit-hooks \
check-pipeline-name-only-steps \
--files test-data/pipeline-name-only-bad.yaml
```

## Test files

### pipeline-name-only-bad.yaml
- **Tests**: `check-pipeline-name-only-steps`
- **Expected**: Should FAIL
- **Issues**: Contains pipeline steps that have only a `name` field without `uses` or other details

### pipeline-name-only-good.yaml
- **Tests**: `check-pipeline-name-only-steps`
- **Expected**: Should PASS
- **Issues**: None - all pipeline steps are properly formatted
38 changes: 38 additions & 0 deletions test-data/pipeline-name-only-bad.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package:
name: test-package
version: "1.0.0"
epoch: 0
description: "Test package with incorrectly formatted pipeline steps"
copyright:
- license: Apache-2.0

environment:
contents:
packages:
- busybox

pipeline:
- name: test/go-fips-check # BAD: This should be 'uses' not 'name'
- name: "Configure build"
uses: autoconf/configure
with:
opts: --enable-shared
- uses: autoconf/make

test:
pipeline:
- name: run-tests # BAD: Only has name, no uses
- uses: test/daemon-check-output
with:
expected_output: |
Server started

subpackages:
- name: test-subpkg
description: "Subpackage with bad pipeline"
pipeline:
- name: bad-subpkg-step # BAD: Only has name
- uses: split/dev
test:
pipeline:
- name: subpkg-test-step # BAD: Only has name in test section
48 changes: 48 additions & 0 deletions test-data/pipeline-name-only-good.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package:
name: test-package-good
version: "1.0.0"
epoch: 0
description: "Test package with correctly formatted pipeline steps"
copyright:
- license: Apache-2.0

environment:
contents:
packages:
- busybox
- go-fips-1.22

pipeline:
- uses: test/go-fips-check # GOOD: Uses 'uses' instead of 'name'
- name: "Configure build"
uses: autoconf/configure # GOOD: Has both name and uses
with:
opts: --enable-shared
- uses: autoconf/make # GOOD: Just uses is fine
- name: "Run custom script" # GOOD: Has name and runs
runs: |
echo "Building package"
make install

test:
pipeline:
- uses: test/go-fips-check # GOOD: Properly uses 'uses'
- name: "Test daemon output"
uses: test/daemon-check-output # GOOD: Has both name and uses
with:
expected_output: |
Server started
- uses: test/emptypackage # GOOD: Just uses

subpackages:
- name: test-subpkg-good
description: "Subpackage with correct pipeline"
pipeline:
- uses: split/dev # GOOD: Uses 'uses'
- name: "Move files"
runs: | # GOOD: Has name and runs
mkdir -p ${{targets.subpkgdir}}/usr/bin
mv usr/bin/tool ${{targets.subpkgdir}}/usr/bin/
test:
pipeline:
- uses: test/emptypackage # GOOD: Uses 'uses' in test section
Loading