diff --git a/README.md b/README.md index e277672..be4dc0f 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,16 @@ with its CIS result and SBOM attached (NetOrk/netork#1390). image: registry.netork.io/netork/engine:main-abc1234 arch: amd64 +# a base's nightly, to decide whether its pin moves: +- name: Judge it against the pin + id: verdict + uses: https://git.netork.io/NetOrk/image-tools/actions/verdict@ + env: + NETORK_LICENSE_KEY: ${{ secrets.NETORK_LICENSE_KEY }} + with: + current: registry.netork.io/netork/base-os:pinned + candidate: registry.netork.io/netork/base-os:13-20261022.3-amd64 + # push it, then: - name: Sign it uses: https://git.netork.io/NetOrk/image-tools/actions/sign@ diff --git a/actions/verdict/action.yml b/actions/verdict/action.yml new file mode 100644 index 0000000..3b2942a --- /dev/null +++ b/actions/verdict/action.yml @@ -0,0 +1,39 @@ +name: Judge a rebuild against the pin +description: >- + Whether a candidate improves on the pinned image, by netOrk's matcher in + kb-server (image_tools/verify.py). Writes verdict.json and report.md, prints + the report, and fails when no verdict could be reached. The caller passes + NETORK_LICENSE_KEY, and TRIVY_USERNAME/TRIVY_PASSWORD for the registry, as env. +inputs: + current: + description: The pinned image + required: true + candidate: + description: The rebuild + required: true + packages: + description: The packages the image may hold, for the CIS part of the verdict + default: packages.allow +outputs: + improved: + description: '"true" when the candidate improves on the pin' + value: ${{ steps.verdict.outputs.improved }} +runs: + using: composite + steps: + - id: verdict + shell: bash + env: + CURRENT: ${{ inputs.current }} + CANDIDATE: ${{ inputs.candidate }} + PACKAGES: ${{ inputs.packages }} + PYTHONPATH: ${{ github.action_path }}/../.. + run: | + python3 -m image_tools.verify "$CURRENT" "$CANDIDATE" --packages "$PACKAGES" \ + --json verdict.json --report report.md + # Into the log: Gitea shows no step summary. + cat report.md + cat report.md >> "$GITHUB_STEP_SUMMARY" + python3 -c "import json; print('improved=' + str(json.load(open('verdict.json'))['improved']).lower())" >> "$GITHUB_OUTPUT" + # A verdict that could not be reached is no "same": the step fails, so it is seen. + python3 -c "import json, sys; e = json.load(open('verdict.json'))['error']; sys.exit(f'no verdict: {e}' if e else 0)" diff --git a/tests/test_actions.py b/tests/test_actions.py index 5b630ba..3cad5c9 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -17,8 +17,8 @@ ROOT = Path(__file__).resolve().parents[1] ACTIONS = sorted((ROOT / "actions").glob("*/action.yml")) -def test_the_three_actions(): - assert [a.parent.name for a in ACTIONS] == ["judge", "sign", "verify-base"] +def test_the_four_actions(): + assert [a.parent.name for a in ACTIONS] == ["judge", "sign", "verdict", "verify-base"] @pytest.mark.parametrize("action", ACTIONS, ids=lambda a: a.parent.name) @@ -36,3 +36,13 @@ def test_signing_needs_its_secrets_from_the_caller(): text = (ROOT / "actions/sign/action.yml").read_text() assert "COSIGN_PRIVATE_KEY_B64" in text and "secrets." not in text + + +def test_a_verdict_that_could_not_be_reached_fails_the_step(): + """A silent "same" hid an empty NETORK_LICENSE_KEY for two nights (2026-10-21/22).""" + spec = yaml.safe_load((ROOT / "actions/verdict/action.yml").read_text()) + [step] = spec["runs"]["steps"] + + assert spec["outputs"]["improved"]["value"] == "${{ steps.verdict.outputs.improved }}" + assert "['error']" in step["run"] and "sys.exit(" in step["run"] + assert "cat report.md" in step["run"].splitlines()