diff --git a/README.md b/README.md index cf1fa5c..9184c3d 100644 --- a/README.md +++ b/README.md @@ -576,10 +576,25 @@ Single artifact: timeout: 8m ``` +Command mode — run your project's own loader on every matrix kernel (the +per-kernel verdict is the loader's exit code), against the built-in +[library of known-tricky vendor kernels](docs/kernel-quirk-library.md): + +```yaml +- uses: Kernel-Guard/bpfcompat@v0.2.0 + with: + command: $BPFCOMPAT_BIN --self-test + command-binary: build/myloader # static or fully self-contained binary + matrix: quirk-library # bare name -> matrices/ shipped with the action + out: reports/bpfcompat.json +``` + Marketplace quick start: -1. Add a self-hosted Linux runner with KVM enabled. -2. Commit compiled `.bpf.o` artifacts, manifests, and a matrix YAML. +1. Use a stock `ubuntu-latest` runner (it exposes `/dev/kvm`); a self-hosted + KVM runner is only needed for wide matrices or ARM64. +2. Commit compiled `.bpf.o` artifacts (or point `command` at your loader), + plus a matrix YAML — or use a built-in matrix name like `quirk-library`. 3. Use the action in CI to produce JSON, Markdown, and job-summary evidence. 4. Treat exit code `2` as a compatibility gate failure. diff --git a/action.yml b/action.yml index 7d4e741..abe136b 100644 --- a/action.yml +++ b/action.yml @@ -11,8 +11,20 @@ inputs: description: Path to compiled .bpf.o artifact. required: false default: "" + command: + description: "Command-mode validation: shell command run as root inside each kernel VM; the per-kernel verdict is its exit code. Use instead of (or alongside) artifact to validate via your project's own loader binary. Exposes $BPFCOMPAT_BIN, $BPFCOMPAT_ARTIFACT, and $BPFCOMPAT_REMOTE_ROOT." + required: false + default: "" + command-binary: + description: Local loader executable shipped into each guest and exposed to command as $BPFCOMPAT_BIN. + required: false + default: "" + command-expect-exit: + description: Exit code that counts as a pass in command mode. + required: false + default: "0" matrix: - description: Path to matrix YAML. + description: "Path to matrix YAML. A bare name (for example quirk-library) that does not exist in the caller repository resolves to the matching matrices/.yaml shipped with the action." required: false default: "" out: @@ -125,6 +137,12 @@ runs: - name: Run bpfcompat shell: bash + env: + # Free-text inputs go through the environment, never inline + # interpolation, so they cannot inject host-side shell syntax. + INPUT_COMMAND: ${{ inputs.command }} + INPUT_COMMAND_BINARY: ${{ inputs.command-binary }} + INPUT_COMMAND_EXPECT_EXIT: ${{ inputs.command-expect-exit }} run: | set -euo pipefail @@ -146,6 +164,19 @@ runs: artifact_path="$(resolve_path "${{ inputs.artifact }}")" matrix_path="$(resolve_path "${{ inputs.matrix }}")" + command_binary_path="$(resolve_path "$INPUT_COMMAND_BINARY")" + + # Built-in matrix convenience: a bare name (e.g. quirk-library) that + # does not exist in the caller repo resolves to the matrices/ directory + # shipped with the action. + if [[ -n "$matrix_path" && ! -f "$matrix_path" ]]; then + matrix_input="${{ inputs.matrix }}" + if [[ "$matrix_input" != /* && -f "$action_root/matrices/${matrix_input}.yaml" ]]; then + matrix_path="$action_root/matrices/${matrix_input}.yaml" + elif [[ "$matrix_input" != /* && -f "$action_root/$matrix_input" ]]; then + matrix_path="$action_root/$matrix_input" + fi + fi out_path="$(resolve_path "${{ inputs.out }}")" manifest_path="$(resolve_path "${{ inputs.manifest }}")" markdown_path="$(resolve_path "${{ inputs.markdown }}")" @@ -218,8 +249,8 @@ runs: exit "$status" fi - if [[ -z "$artifact_path" ]]; then - echo "::error::artifact is required unless suite is set" + if [[ -z "$artifact_path" && -z "$INPUT_COMMAND" ]]; then + echo "::error::artifact or command is required unless suite is set" exit 1 fi if [[ -z "$matrix_path" ]]; then @@ -231,10 +262,18 @@ runs: exit 1 fi - if [[ ! -f "$artifact_path" ]]; then + if [[ -n "$artifact_path" && ! -f "$artifact_path" ]]; then echo "::error::artifact not found: $artifact_path" exit 1 fi + if [[ -n "$command_binary_path" && ! -f "$command_binary_path" ]]; then + echo "::error::command-binary not found: $command_binary_path" + exit 1 + fi + if [[ -n "$command_binary_path" && -z "$INPUT_COMMAND" ]]; then + echo "::error::command-binary requires command" + exit 1 + fi if [[ ! -f "$matrix_path" ]]; then echo "::error::matrix not found: $matrix_path" exit 1 @@ -249,7 +288,6 @@ runs: cmd=( "$binary_path" test - --artifact "$artifact_path" --matrix "$matrix_path" --out "$out_path" --timeout "${{ inputs.timeout }}" @@ -257,6 +295,18 @@ runs: --concurrency "${{ inputs.concurrency }}" ) + if [[ -n "$artifact_path" ]]; then + cmd+=(--artifact "$artifact_path") + fi + if [[ -n "$INPUT_COMMAND" ]]; then + cmd+=(--command "$INPUT_COMMAND") + if [[ -n "$command_binary_path" ]]; then + cmd+=(--command-binary "$command_binary_path") + fi + if [[ -n "$INPUT_COMMAND_EXPECT_EXIT" && "$INPUT_COMMAND_EXPECT_EXIT" != "0" ]]; then + cmd+=(--command-expect-exit "$INPUT_COMMAND_EXPECT_EXIT") + fi + fi if [[ -n "$manifest_path" ]]; then cmd+=(--manifest "$manifest_path") fi diff --git a/docs/command-validation.md b/docs/command-validation.md index 1f4723c..4864afc 100644 --- a/docs/command-validation.md +++ b/docs/command-validation.md @@ -100,6 +100,28 @@ inside it freely: pipes, `&&`, redirects. artifact identity is content-addressed from the command string (`command://`), so `compare`/history still work. +## GitHub Action + +The composite action supports command mode directly, so a project can gate CI +on its own loader with one step. A bare `matrix` name resolves to the +`matrices/` directory shipped with the action — `quirk-library` gives you the +[library of known-tricky vendor kernels](kernel-quirk-library.md) with no file +to copy: + +```yaml +- uses: Kernel-Guard/bpfcompat@v0.2.0 + with: + command: $BPFCOMPAT_BIN --self-test + command-binary: build/myloader + command-expect-exit: "0" # optional, default 0 + matrix: quirk-library + out: reports/bpfcompat.json +``` + +The `command` string is passed to the runner through the environment (never +interpolated into the action's shell), and inside the guest it is executed as a +single quoted `bash -lc` operand, exactly as in the CLI flow. + ## Scope / limitations (first cut) - Command mode currently supports the **`vm`** runner only (the default). It is