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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__pycache__/
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-for-go-fips-test
name: check for go-fips test
description: Check that packages using go-fips have corresponding go-fips tests
entry: check-for-go-fips-test
language: python
stages:
- pre-commit
- manual
types:
- yaml
2 changes: 2 additions & 0 deletions example.pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ repos:
(?x)^(
[^/]+\.ya?ml # matches .yaml or .yml files at the top level only
)$
- id: check-for-go-fips-test
files: '^[^.][^/]*\.yaml$' # matches non-hidden .yaml files at the top level only
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be more efficient to use this to identify the go-fips packages that need to be parsed. If we can filter out packages at this level, we needn't spawn another python interpreter to run the check. Of course, if it might be the case that only a subpackage will match, then that won't work.

- repo: https://github.com/chainguard-dev/yam
rev: 768695300c5f663012a77911eb4920c12e5ed2e5 # frozen: v0.2.26
hooks:
Expand Down
125 changes: 125 additions & 0 deletions pre_commit_hooks/check_for_go_fips_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
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_go_fips_compliance(melange_cfg: dict[str, Any]) -> tuple[bool, list[str]]:
"""
Check if all go-fips usages have corresponding tests.
Returns (is_compliant, list_of_missing_tests).
"""
issues = []

# Check if main package uses go-fips
main_uses_fips = False
main_has_test = False

Copy link
Contributor

Choose a reason for hiding this comment

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

I feel like we shouldn't need to duplicate the code between main package parsing and subpkg parsing. See the shellcheck test, where it puts the main package and any sub package in a list, and just iterates through them all the same way. We should probably consider creating a library class that just does stuff like this for us so we can share it.

# Check environment packages for any go-fips variant
env_packages = (
melange_cfg.get("environment", {}).get("contents", {}).get("packages", [])
)
for pkg in env_packages:
if pkg.startswith("go-fips"):
main_uses_fips = True
break

# Check main pipeline steps for go/build with go-package: go-fips*
pipelines = melange_cfg.get("pipeline", [])
for step in pipelines:
if step.get("uses") == "go/build":
go_package = step.get("with", {}).get("go-package", "")
if go_package.startswith("go-fips"):
main_uses_fips = True
break

# Check main test section
test_section = melange_cfg.get("test", {})
test_pipelines = test_section.get("pipeline", [])
main_has_emptypackage_test = False
for step in test_pipelines:
if step.get("uses") == "test/go-fips-check":
main_has_test = True
elif step.get("uses") == "test/emptypackage":
main_has_emptypackage_test = True

# If main package has emptypackage test, it doesn't need go-fips test
if main_uses_fips and not main_has_test and not main_has_emptypackage_test:
issues.append("main package uses go-fips but lacks test/go-fips-check")

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

# Check subpackage pipelines for go-fips usage
subpkg_pipelines = subpkg.get("pipeline", [])
for step in subpkg_pipelines:
if step.get("uses") == "go/build":
go_package = step.get("with", {}).get("go-package", "")
if go_package.startswith("go-fips"):
subpkg_uses_fips = True
break

# Check subpackage test sections
subpkg_test_section = subpkg.get("test", {})
subpkg_test_pipelines = subpkg_test_section.get("pipeline", [])
subpkg_has_emptypackage_test = False
for step in subpkg_test_pipelines:
if step.get("uses") == "test/go-fips-check":
subpkg_has_test = True
elif step.get("uses") == "test/emptypackage":
subpkg_has_emptypackage_test = True

# If subpackage has emptypackage test, it doesn't need go-fips test
if (
subpkg_uses_fips
and not subpkg_has_test
and not subpkg_has_emptypackage_test
):
issues.append(
f"subpackage '{subpkg_name}' uses go-fips but lacks test/go-fips-check",
)

return len(issues) == 0, issues


def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(
description="Check that packages using go-fips have corresponding go-fips tests",
)
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_compliant, issues = check_go_fips_compliance(melange_cfg)
if not is_compliant:
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-for-go-fips-test = pre_commit_hooks.check_for_go_fips_test:main

[bdist_wheel]
universal = True
Expand Down
Loading