From de1d72b7a2ce62670d3e61b143237df0edd24007 Mon Sep 17 00:00:00 2001 From: Amber Arcadia Date: Mon, 11 Aug 2025 12:31:34 -0400 Subject: [PATCH 1/5] Add check-for-go-fips-test pre-commit hook Implements GitHub issue requirements: - Detects packages using go-fips in environment or pipeline - Verifies they have corresponding go-fips test (uses: test/go-fips-check) - Reports failures for packages missing the required test --- .gitignore | 1 + .pre-commit-hooks.yaml | 10 +++ example.pre-commit-config.yaml | 2 + pre_commit_hooks/check_for_go_fips_test.py | 83 ++++++++++++++++++++++ setup.cfg | 1 + 5 files changed, 97 insertions(+) create mode 100644 .gitignore create mode 100644 pre_commit_hooks/check_for_go_fips_test.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba0430d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ \ No newline at end of file diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index c78a274..f0022bb 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -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 diff --git a/example.pre-commit-config.yaml b/example.pre-commit-config.yaml index e10000a..7653a6c 100644 --- a/example.pre-commit-config.yaml +++ b/example.pre-commit-config.yaml @@ -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 - repo: https://github.com/chainguard-dev/yam rev: 768695300c5f663012a77911eb4920c12e5ed2e5 # frozen: v0.2.26 hooks: diff --git a/pre_commit_hooks/check_for_go_fips_test.py b/pre_commit_hooks/check_for_go_fips_test.py new file mode 100644 index 0000000..2183846 --- /dev/null +++ b/pre_commit_hooks/check_for_go_fips_test.py @@ -0,0 +1,83 @@ +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 uses_go_fips(melange_cfg: dict[str, Any]) -> bool: + """Check if package uses go-fips.""" + # Check environment packages + env_packages = melange_cfg.get("environment", {}).get("contents", {}).get("packages", []) + if "go-fips" in env_packages: + return True + + # Check 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": + if step.get("with", {}).get("go-package") == "go-fips": + return True + + # Check subpackage pipelines + for subpkg in melange_cfg.get("subpackages", []): + subpkg_pipelines = subpkg.get("pipeline", []) + for step in subpkg_pipelines: + if step.get("uses") == "go/build": + if step.get("with", {}).get("go-package") == "go-fips": + return True + + return False + + +def has_go_fips_test(melange_cfg: dict[str, Any]) -> bool: + """Check if package has go-fips test.""" + test_section = melange_cfg.get("test", {}) + test_pipelines = test_section.get("pipeline", []) + + for step in test_pipelines: + if step.get("uses") == "test/go-fips-check": + return True + + return False + + +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 + + if uses_go_fips(melange_cfg): + if not has_go_fips_test(melange_cfg): + print( + f"{filename}: Package uses go-fips but does not have " + "a corresponding go-fips test (add '- uses: test/go-fips-check' to test pipeline)" + ) + retval = 1 + + return retval + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index b508882..21f86b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 From 62013765757bf5b4982c8b007dc6744c0d3faa86 Mon Sep 17 00:00:00 2001 From: Amber Arcadia Date: Mon, 11 Aug 2025 13:55:15 -0400 Subject: [PATCH 2/5] Improve go-fips detection for all variants and subpackages - Detect any go-fips variant (go-fips, go-fips-md5, etc.) using startswith() - Check each package/subpackage independently for compliance - Require matching tests for each go-fips usage (not just any test) - Provide detailed error messages specifying which component lacks tests - Handle subpackage test sections properly Fixes comprehensive validation of go-fips test requirements. --- pre_commit_hooks/check_for_go_fips_test.py | 90 ++++++++++++++-------- 1 file changed, 59 insertions(+), 31 deletions(-) diff --git a/pre_commit_hooks/check_for_go_fips_test.py b/pre_commit_hooks/check_for_go_fips_test.py index 2183846..10c2af7 100644 --- a/pre_commit_hooks/check_for_go_fips_test.py +++ b/pre_commit_hooks/check_for_go_fips_test.py @@ -10,41 +10,71 @@ yaml = ruamel.yaml.YAML(typ="safe") -def uses_go_fips(melange_cfg: dict[str, Any]) -> bool: - """Check if package uses go-fips.""" - # Check environment packages +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 + + # Check environment packages for any go-fips variant env_packages = melange_cfg.get("environment", {}).get("contents", {}).get("packages", []) - if "go-fips" in env_packages: - return True + for pkg in env_packages: + if pkg.startswith("go-fips"): + main_uses_fips = True + break - # Check pipeline steps for go/build with go-package: go-fips + # 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": - if step.get("with", {}).get("go-package") == "go-fips": - return True - - # Check subpackage pipelines - for subpkg in melange_cfg.get("subpackages", []): - subpkg_pipelines = subpkg.get("pipeline", []) - for step in subpkg_pipelines: - if step.get("uses") == "go/build": - if step.get("with", {}).get("go-package") == "go-fips": - return True + go_package = step.get("with", {}).get("go-package", "") + if go_package.startswith("go-fips"): + main_uses_fips = True + break - return False - - -def has_go_fips_test(melange_cfg: dict[str, Any]) -> bool: - """Check if package has go-fips test.""" + # Check main test section test_section = melange_cfg.get("test", {}) test_pipelines = test_section.get("pipeline", []) - for step in test_pipelines: if step.get("uses") == "test/go-fips-check": - return True + main_has_test = True + break + + if main_uses_fips and not main_has_test: + issues.append("main package uses go-fips but lacks test/go-fips-check") - return False + # 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", []) + for step in subpkg_test_pipelines: + if step.get("uses") == "test/go-fips-check": + subpkg_has_test = True + break + + if subpkg_uses_fips and not subpkg_has_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: @@ -68,13 +98,11 @@ def main(argv: Sequence[str] | None = None) -> int: if not melange_cfg: continue - if uses_go_fips(melange_cfg): - if not has_go_fips_test(melange_cfg): - print( - f"{filename}: Package uses go-fips but does not have " - "a corresponding go-fips test (add '- uses: test/go-fips-check' to test pipeline)" - ) - retval = 1 + 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 From 0b581eece7c61fe29b79b84512410ee61c73b870 Mon Sep 17 00:00:00 2001 From: Amber Arcadia Date: Tue, 12 Aug 2025 10:18:23 -0400 Subject: [PATCH 3/5] Linting --- pre_commit_hooks/check_for_go_fips_test.py | 42 ++++++++++++---------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/pre_commit_hooks/check_for_go_fips_test.py b/pre_commit_hooks/check_for_go_fips_test.py index 10c2af7..f8bc3b2 100644 --- a/pre_commit_hooks/check_for_go_fips_test.py +++ b/pre_commit_hooks/check_for_go_fips_test.py @@ -16,18 +16,20 @@ def check_go_fips_compliance(melange_cfg: dict[str, Any]) -> tuple[bool, list[st Returns (is_compliant, list_of_missing_tests). """ issues = [] - + # Check if main package uses go-fips main_uses_fips = False main_has_test = False - + # Check environment packages for any go-fips variant - env_packages = melange_cfg.get("environment", {}).get("contents", {}).get("packages", []) + 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: @@ -36,7 +38,7 @@ def check_go_fips_compliance(melange_cfg: dict[str, Any]) -> tuple[bool, list[st 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", []) @@ -44,16 +46,16 @@ def check_go_fips_compliance(melange_cfg: dict[str, Any]) -> tuple[bool, list[st if step.get("uses") == "test/go-fips-check": main_has_test = True break - + if main_uses_fips and not main_has_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: @@ -62,7 +64,7 @@ def check_go_fips_compliance(melange_cfg: dict[str, Any]) -> tuple[bool, list[st 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", []) @@ -70,22 +72,24 @@ def check_go_fips_compliance(melange_cfg: dict[str, Any]) -> tuple[bool, list[st if step.get("uses") == "test/go-fips-check": subpkg_has_test = True break - + if subpkg_uses_fips and not subpkg_has_test: - issues.append(f"subpackage '{subpkg_name}' uses go-fips but lacks test/go-fips-check") - + 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" + 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: @@ -94,18 +98,18 @@ def main(argv: Sequence[str] | None = None) -> int: 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()) \ No newline at end of file + sys.exit(main()) From 5d0c8e7e2f3c26443ba81d3f6feaa18441643184 Mon Sep 17 00:00:00 2001 From: Amber Arcadia Date: Tue, 12 Aug 2025 12:52:09 -0400 Subject: [PATCH 4/5] Avoid adding go-fips test to empty packages --- pre_commit_hooks/check_for_go_fips_test.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pre_commit_hooks/check_for_go_fips_test.py b/pre_commit_hooks/check_for_go_fips_test.py index f8bc3b2..e6dda74 100644 --- a/pre_commit_hooks/check_for_go_fips_test.py +++ b/pre_commit_hooks/check_for_go_fips_test.py @@ -42,12 +42,15 @@ def check_go_fips_compliance(melange_cfg: dict[str, Any]) -> tuple[bool, list[st # 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 - break + elif step.get("uses") == "test/emptypackage": + main_has_emptypackage_test = True - if main_uses_fips and not main_has_test: + # 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 @@ -68,12 +71,15 @@ def check_go_fips_compliance(melange_cfg: dict[str, Any]) -> tuple[bool, list[st # 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 - break + elif step.get("uses") == "test/emptypackage": + subpkg_has_emptypackage_test = True - if subpkg_uses_fips and not subpkg_has_test: + # 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", ) From 946abf3c9cfb63ded847ddbc228600b515f59c8f Mon Sep 17 00:00:00 2001 From: Amber Arcadia Date: Tue, 12 Aug 2025 13:04:28 -0400 Subject: [PATCH 5/5] Linting --- pre_commit_hooks/check_for_go_fips_test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pre_commit_hooks/check_for_go_fips_test.py b/pre_commit_hooks/check_for_go_fips_test.py index e6dda74..0fad3df 100644 --- a/pre_commit_hooks/check_for_go_fips_test.py +++ b/pre_commit_hooks/check_for_go_fips_test.py @@ -79,7 +79,11 @@ def check_go_fips_compliance(melange_cfg: dict[str, Any]) -> tuple[bool, list[st 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: + 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", )