From d09a0cf3210cb3ad54fb356edb9d5a66cc502fee Mon Sep 17 00:00:00 2001 From: Ilan Harel Date: Mon, 9 Nov 2026 13:14:40 +0200 Subject: [PATCH] docs(actions): setup and usage guide, generated inputs/outputs tables, docs-in-same-PR rule Extend docs/GITHUB_ACTIONS.md into a setup and usage guide (prerequisites, quick start, choosing a ref/version, per-action reference, recipes, pinning, troubleshooting, real example). Inputs/outputs tables are generated from actions/*/action.yml by scripts/gen-actions-docs.sh; CI and ci-local.sh run it with --check. Add a docs-in-the-same-PR rule with a code-area-to-doc map to AGENTS.md and CONTRIBUTING.md, and PR template checklist items. Fix stale docs: check@v0.1.0-rc.3 is broken for consumers (#65), so quick starts use setup+drift+lint at that tag; correct version comments on pinned SHAs (checkout v7.0.1, cache v4.3.0, upload-artifact v7.0.2); setup's version description after #66; document that latest resolves only stable releases. Closes #74 Signed-off-by: Ilan Harel --- .github/pull_request_template.md | 2 + .github/workflows/actions-selftest.yml | 8 +- .github/workflows/ci.yml | 2 + AGENTS.md | 16 + CHANGELOG.md | 1 + CONTRIBUTING.md | 5 + README.md | 11 +- actions/check/action.yml | 6 +- actions/generate/action.yml | 2 +- actions/setup/action.yml | 10 +- docs/DEVELOPING.md | 5 +- docs/GITHUB_ACTIONS.md | 408 ++++++++++++++++++------- scripts/ci-local.sh | 3 +- scripts/gen-actions-docs.sh | 137 +++++++++ 14 files changed, 485 insertions(+), 131 deletions(-) create mode 100755 scripts/gen-actions-docs.sh diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index ed90c20..1064f30 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,4 +12,6 @@ Closes # - [ ] `scripts/ci-local.sh` passes (`TOOLCHAIN=1.94 scripts/ci-local.sh`) - [ ] Tests added or updated (`tests/cli.rs`, fixtures, golden files) - [ ] Docs regenerated if directives or flags changed (`scripts/gen-docs.sh`) +- [ ] Docs updated in this PR for every behaviour change (mapping in `AGENTS.md`: `actions/*` → `docs/GITHUB_ACTIONS.md` + `scripts/gen-actions-docs.sh`, CLI → `README.md`, release process → `docs/RELEASING.md`) +- [ ] `CHANGELOG.md` has a line under `## [Unreleased]` if users will notice the change - [ ] No secrets in code, fixtures or output diff --git a/.github/workflows/actions-selftest.yml b/.github/workflows/actions-selftest.yml index 8aa02ab..91db327 100644 --- a/.github/workflows/actions-selftest.yml +++ b/.github/workflows/actions-selftest.yml @@ -26,7 +26,7 @@ jobs: release-setup-smoke: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4.2.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install from release uses: ./actions/setup with: @@ -43,7 +43,7 @@ jobs: matrix: os: [ubuntu-24.04, macos-15] steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4.2.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Check (setup + drift + lint) uses: ./actions/check with: @@ -74,7 +74,7 @@ jobs: drift-failure: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4.2.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - uses: ./actions/setup with: version: source @@ -107,7 +107,7 @@ jobs: matrix: version: [source, v0.1.0-rc.3] steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4.2.2 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: path: annotate-src - name: Create consumer fixture repo diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d0dc7a..2c04d24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,8 @@ jobs: run: cargo test --locked --test gateway_policies - name: docs are up to date run: scripts/gen-docs.sh --check + - name: GitHub Actions docs match action.yml + run: scripts/gen-actions-docs.sh --check audit: runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index 16fc65d..bc06846 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,3 +26,19 @@ subprocesses; add directives with reasons; run `scan`, `generate`, `lint`, `drif - Human output: `src/report.rs` renders lint; `src/render.rs` renders `scan`, `diff-logs` and the `generate` summary in the same style (shared `Style`, icons, `--format/--color/--no-icons`). `--format compact` and `--format json` must stay byte-stable and `generate` must never put anything but the policy on stdout. Snapshots live in `tests/golden` (`UPDATE_GOLDEN=1 cargo test`). + +## Docs must be updated in the same PR +A change is not done until the docs that describe it match. Update them in the same pull request: + +| When you change | Update | +| --- | --- | +| `actions/*` (inputs, outputs, behaviour) or `actions/lib/*` | `docs/GITHUB_ACTIONS.md`: run `scripts/gen-actions-docs.sh` for the tables, then fix the prose, recipes and troubleshooting; the README snippet if usage changed | +| CLI commands, flags, exit codes or output | `README.md` and `scripts/gen-docs.sh` (regenerates `docs/CLI.md`); `docs/DEVELOPING.md` if a workflow changes | +| Directives or attributes (`src/directive.rs`, `src/spec.rs`, `src/generate.rs`) | `scripts/gen-docs.sh` (regenerates `docs/DIRECTIVES.md`) and `skills/annotate-openshell-policy/SKILL.md` if authoring guidance changes | +| Release process (`scripts/release.sh`, `release.yml`, `install.sh`, packaging) | `docs/RELEASING.md` | +| Pinned OpenShell version (`Cargo.toml`) | every doc that names the version (`git grep -n 'v0\.1\.[0-9]'`) | +| Branch, issue or CI rules | `CONTRIBUTING.md`, `docs/DEVELOPING.md`, `.github/pull_request_template.md` | +| Anything user-visible | a line under `## [Unreleased]` in `CHANGELOG.md` | + +CI enforces the generated parts (`scripts/gen-docs.sh --check`, `scripts/gen-actions-docs.sh --check`); the rest is on +the author and the reviewer. diff --git a/CHANGELOG.md b/CHANGELOG.md index cb24532..9537e65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ version section and adds the squash-merged PR titles since the previous tag. Ver - chore(audit): ignore RUSTSEC-2026-0459 (`libsqlite3-sys` 0.37.0, pinned by OpenShell v0.1.7 through `sqlx` 0.9.0) in `.cargo/audit.toml` until OpenShell upgrades it (#76) - chore(deps): build against OpenShell v0.1.7 (`openshell-policy-schema`, `openshell-policy`); docs and generated policy headers now name v0.1.7 +- docs(actions): `docs/GITHUB_ACTIONS.md` is now a setup and usage guide (versions, recipes, troubleshooting); its inputs/outputs tables are generated from `action.yml` by `scripts/gen-actions-docs.sh` and checked in CI - fix(diff-logs): ignore OCSF Network Activity Refuse records (policy DNS `policy_dns_ineligible`) that have no port, so OCSF JSON and shorthand logs report the same denials ## [0.1.0-rc.3] - 2026-11-09 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5e71b41..3b72d75 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,10 @@ GitHub issues are how we track all work: bugs, features and follow-ups. `tests/fixtures/`). If generated output changes, review `tests/fixtures/sample/policy.expected.yaml` by hand (its header carries `@VERSION@`, which the tests replace with the crate version). - Never emit a field that the pinned `openshell-policy-schema` rejects. - No secrets in code, fixtures or test output. +- Update the docs in the same pull request. The table in [AGENTS.md](AGENTS.md#docs-must-be-updated-in-the-same-pr) + maps code areas to docs: `actions/*` to `docs/GITHUB_ACTIONS.md`, CLI flags and commands to `README.md` and the + generated `docs/CLI.md`, directives to the generated `docs/DIRECTIVES.md`, the release process to `docs/RELEASING.md`, + and user-visible changes to `CHANGELOG.md`. ## Checks (CI runs the same) @@ -52,6 +56,7 @@ cargo fmt --check cargo clippy --all-targets --locked -- -D warnings cargo test --locked scripts/gen-docs.sh --check # regenerate with scripts/gen-docs.sh when directives or flags change +scripts/gen-actions-docs.sh --check # regenerate with scripts/gen-actions-docs.sh when an action.yml changes ``` ## Developer Certificate of Origin (DCO) diff --git a/README.md b/README.md index 326b643..c91a460 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,22 @@ Status: early prototype (version 0.1.0). Targets OpenShell policy schema v1 Downstream repositories can run drift, lint, generate, and diff-logs in CI with composite actions under [`actions/`](actions/). Pin a release tag on every `uses:` line (same ref for all steps). See -[docs/GITHUB_ACTIONS.md](docs/GITHUB_ACTIONS.md) for inputs, outputs, and copy-paste workflows. +[docs/GITHUB_ACTIONS.md](docs/GITHUB_ACTIONS.md) for setup, choosing a version, inputs and outputs, recipes and +troubleshooting. ```yaml -- uses: ilanh/openshell-annotate/actions/check@v0.1.0-rc.3 +- uses: ilanh/openshell-annotate/actions/setup@v0.1.0-rc.3 +- uses: ilanh/openshell-annotate/actions/drift@v0.1.0-rc.3 with: policy: openshell/policy.yaml providers: openshell/providers.json +- uses: ilanh/openshell-annotate/actions/lint@v0.1.0-rc.3 + with: + policy: openshell/policy.yaml ``` +The combined `check` action works from other repositories on `main` and in releases after `v0.1.0-rc.3` (#65). + ## Install Prebuilt binaries (Linux and macOS, x86_64 and arm64) and a container image are attached to each diff --git a/actions/check/action.yml b/actions/check/action.yml index 7ca04b1..6dd952e 100644 --- a/actions/check/action.yml +++ b/actions/check/action.yml @@ -6,7 +6,7 @@ branding: inputs: version: - description: Release tag for setup (`latest`, a tag, or `source` to build this repository). + description: CLI version, as for `setup` (a release tag, `latest` for the newest stable release, or `source` to build the action's own ref). required: false default: v0.1.0-rc.3 root: @@ -21,7 +21,7 @@ inputs: required: false default: "" profiles-dir: - description: Provider profiles directory. + description: Provider profiles directory for drift (`--profiles-dir`). required: false default: "" failure-threshold: @@ -52,7 +52,7 @@ runs: - name: Restore cache if: inputs.version != 'source' id: cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.2.2 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ steps.dir.outputs.dir }}/openshell-annotate key: openshell-annotate-${{ runner.os }}-${{ runner.arch }}-${{ inputs.version }} diff --git a/actions/generate/action.yml b/actions/generate/action.yml index ab4e77b..0f358ee 100644 --- a/actions/generate/action.yml +++ b/actions/generate/action.yml @@ -74,7 +74,7 @@ runs: - name: Upload artifact if: inputs.upload-artifact == 'true' - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v4.6.2 + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 with: name: ${{ inputs.artifact-name }} path: | diff --git a/actions/setup/action.yml b/actions/setup/action.yml index 3cc8f5d..718c544 100644 --- a/actions/setup/action.yml +++ b/actions/setup/action.yml @@ -7,12 +7,12 @@ branding: inputs: version: description: > - Release tag to install (e.g. v0.1.0-rc.3), `latest`, or `source` to build from the checked-out - repository with cargo (Rust 1.94+ required; for this repo's CI only). + Release tag to install (e.g. v0.1.0-rc.3), `latest` (newest stable release only), or `source` to build + the openshell-annotate sources at the action's own ref with cargo (Rust 1.94+). required: false default: v0.1.0-rc.3 install-dir: - description: Directory for the openshell-annotate binary (added to PATH). + description: Directory for the openshell-annotate binary (added to PATH); empty means `$RUNNER_TOOL_CACHE/openshell-annotate`. required: false default: "" verify-attestation: @@ -20,7 +20,7 @@ inputs: required: false default: "false" cache: - description: Cache the installed binary between runs. + description: Cache the installed binary between runs (keyed on OS, arch and `version`; not used for `source`). required: false default: "true" @@ -45,7 +45,7 @@ runs: - name: Restore cache if: inputs.cache == 'true' && inputs.version != 'source' id: cache - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.2.2 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 with: path: ${{ steps.dir.outputs.dir }}/openshell-annotate key: openshell-annotate-${{ runner.os }}-${{ runner.arch }}-${{ inputs.version }} diff --git a/docs/DEVELOPING.md b/docs/DEVELOPING.md index 368ea5b..29a5abe 100644 --- a/docs/DEVELOPING.md +++ b/docs/DEVELOPING.md @@ -47,7 +47,7 @@ Expected: four result lines, all `ok` (26 unit tests, 30 CLI tests in `tests/cli ## 4. Run what CI runs CI (`.github/workflows/ci.yml`) runs `cargo fmt --check`, `cargo clippy --all-targets --locked -- -D warnings`, -`cargo test --locked` and `scripts/gen-docs.sh --check`, plus `cargo audit`. All but the audit are one command: +`cargo test --locked`, `scripts/gen-docs.sh --check` and `scripts/gen-actions-docs.sh --check`, plus `cargo audit`. All but the audit are one command: ``` TOOLCHAIN=1.94 scripts/ci-local.sh # pinned like CI; omit TOOLCHAIN to use your default toolchain @@ -60,6 +60,8 @@ separate commit (CI will keep using 1.94 until the pin is bumped on purpose in ` `docs/CLI.md` and `docs/DIRECTIVES.md` are generated from the code. After changing a flag, directive or attribute run `scripts/gen-docs.sh` and commit the result; `scripts/gen-docs.sh --check` (and `cargo test`) fail when they are stale. +The inputs/outputs tables in `docs/GITHUB_ACTIONS.md` are generated from `actions/*/action.yml` the same way +(`scripts/gen-actions-docs.sh`). Which docs to update for which change is listed in `AGENTS.md`. ### Upstream compatibility (OpenShell schema changes) @@ -202,5 +204,6 @@ public repo or a paid plan). | `error: failed to select a version` / git fetch of `OpenShell` fails | the schema crate comes from github.com; check network or proxy; `--locked` needs the tag commit recorded in `Cargo.lock` | | clippy errors that CI does not show | newer toolchain; run with `TOOLCHAIN=1.94` or fix the new lint | | `docs are stale` | run `scripts/gen-docs.sh` and commit `docs/` | +| `docs/GITHUB_ACTIONS.md is out of date with actions/*/action.yml` | run `scripts/gen-actions-docs.sh` and commit it; add a `BEGIN/END actions-docs ` section for a new action | | sandbox probes get `403` from `api.github.com` | unauthenticated GitHub API rate limit (60 requests per hour per IP), not a policy decision; wait for the reset or use another host | | `openshell-prover: not found` in `flow.sh` | install the OpenShell 0.1.7 tools; the static commands (`scan`, `generate`, `lint`, `drift`, `diff-logs`) do not need them | diff --git a/docs/GITHUB_ACTIONS.md b/docs/GITHUB_ACTIONS.md index 497f199..c24ccd3 100644 --- a/docs/GITHUB_ACTIONS.md +++ b/docs/GITHUB_ACTIONS.md @@ -1,128 +1,262 @@ # GitHub Actions -Reusable composite actions install a [release](https://github.com/ilanh/openshell-annotate/releases) of -`openshell-annotate` and run the usual CI gates (`drift`, `lint`, `generate`, `diff-logs`). They work on `ubuntu-*` -and `macos-*` runners (linux/darwin, x86_64 and arm64). +Composite actions under [`actions/`](../actions) install `openshell-annotate` and run the usual policy gates in another +repository's CI: `drift` (the committed policy still matches the `@openshell` directives), `lint` (no unannotated calls), +`generate` (rebuild the policy, for example for an image build) and `diff-logs` (compare a sandbox run's logs with the +directives). This page covers setup, choosing a version, every action's inputs and outputs, common recipes and +troubleshooting. -## Versioning +## Prerequisites -Pin the **same ref** for every action in a workflow, for example a release tag: +- A repository with `@openshell` directives and a committed policy, usually `openshell/policy.yaml` (plus + `openshell/providers.json` if you use providers). Create them locally first: + `openshell-annotate generate -o openshell/policy.yaml --providers-out openshell/providers.json`. + See the [README](../README.md) and [DIRECTIVES.md](DIRECTIVES.md). +- A GitHub-hosted `ubuntu-*` or `macos-*` runner (linux or darwin, x86_64 or arm64). Self-hosted runners need `bash`, + `curl` and `jq`, and `cargo` 1.94+ (or `rustup`) for `version: source`. +- Workflow permissions `contents: read`. The actions need no secrets. +- Optional: the GitHub CLI on the runner for `verify-attestation: "true"` (preinstalled on GitHub-hosted runners). -```yaml -uses: ilanh/openshell-annotate/actions/check@v0.1.0-rc.3 -``` +## Quick start -Tags track [releases](https://github.com/ilanh/openshell-annotate/releases). You may also pin a commit SHA for -maximum stability. The `setup` action’s `version` input is the **CLI** version (defaults to `v0.1.0-rc.3`; `latest` is -supported). That is independent of the `@ref` on `uses:` — keep the action ref on a tag/SHA you trust. +Add `.github/workflows/policy.yml`: -Do not rely on `@main` in production workflows; it moves with every merge. +```yaml +name: OpenShell policy -## Quick start (drift + lint) +on: + pull_request: + push: + branches: [main] -```yaml permissions: contents: read jobs: - openshell: + policy: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4.2.2 - - uses: ilanh/openshell-annotate/actions/check@v0.1.0-rc.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: ilanh/openshell-annotate/actions/setup@v0.1.0-rc.3 + - uses: ilanh/openshell-annotate/actions/drift@v0.1.0-rc.3 + with: + policy: openshell/policy.yaml + providers: openshell/providers.json # drop if you have no providers file + - uses: ilanh/openshell-annotate/actions/lint@v0.1.0-rc.3 + with: + policy: openshell/policy.yaml + failure-threshold: warning +``` + +The job fails when the committed policy has drifted from the directives or when lint finds a warning or error. Findings +appear as annotations on the pull request and as tables in the job summary. + +On refs that include [#66](https://github.com/ilanh/openshell-annotate/pull/66) (`main` today, and the next release +after `v0.1.0-rc.3`), the single `check` action does all three steps: + +```yaml + - uses: ilanh/openshell-annotate/actions/check@main # or a release tag newer than v0.1.0-rc.3 with: - root: . + version: source policy: openshell/policy.yaml providers: openshell/providers.json failure-threshold: warning ``` +Do not use `actions/check@v0.1.0-rc.3` from another repository: that release still has the relative `uses:` bug +([#65](https://github.com/ilanh/openshell-annotate/issues/65)). Use `setup` + `drift` + `lint` at that tag instead. + +## Choosing a version + +Two settings decide what runs: + +- **The `@ref` on `uses:`** picks the action code (the YAML and the scripts in `actions/lib`). Use the same ref on every + `uses:` line in a workflow. +- **The `version` input** of `setup` and `check` picks the CLI binary. It defaults to the release the actions were cut + with (`v0.1.0-rc.3`). + +| Goal | `uses:` ref | `version` | Notes | +| --- | --- | --- | --- | +| Reproducible CI (recommended) | a release tag, e.g. `@v0.1.0-rc.3`, or that tag's commit SHA | default, or the same tag | Nothing changes until you bump the ref. | +| Track development | `@main` | `source` | Builds the CLI from the same commit as the actions. Adds about 2–3 minutes per job (no cargo cache) and breaks when `main` breaks, which is the point for an early-warning repo. | +| Newest stable release | a release tag | `latest` | `latest` resolves the newest **stable** release only. Every release so far is a `-rc` prerelease, so `latest` currently fails; pin a tag. | + +`version: source` builds the openshell-annotate sources that ship with the action at its `@ref`, never the caller's +checkout. A prerelease tag (`v0.1.0-rc.3`) must be given explicitly; see +[RELEASING.md](RELEASING.md#installing) for how releases and `latest` work. + +### Pinning and permissions + +- Set `permissions: contents: read` at the workflow or job level. The actions only read the repository and download + public release assets. `verify-attestation` uses the default `GITHUB_TOKEN` through the GitHub CLI. +- Pin third-party actions to a full commit SHA with the version as a comment, as the examples here do + (`actions/checkout@3d3c42e… # v7.0.1`). For the strictest setup, pin `ilanh/openshell-annotate/actions/*` to a commit + SHA too; Dependabot's `github-actions` ecosystem keeps SHA pins and their comments up to date. +- The actions pin their own dependencies (`actions/cache`, `actions/upload-artifact`) to SHAs. +- Avoid `@main` in production gates; it moves with every merge. + ## Actions -| Path | Purpose | +| Action | Purpose | | --- | --- | -| [`actions/setup`](../actions/setup/action.yml) | Install CLI (release or `source`) and add to `PATH` | -| [`actions/drift`](../actions/drift/action.yml) | Fail when committed policy ≠ directives | -| [`actions/lint`](../actions/lint/action.yml) | Annotations + job summary from `lint --format json` | -| [`actions/generate`](../actions/generate/action.yml) | Regenerate policy / providers; optional artifact | -| [`actions/diff-logs`](../actions/diff-logs/action.yml) | Compare sandbox logs to directives | -| [`actions/check`](../actions/check/action.yml) | `setup` + `drift` + `lint` | - -### `actions/setup` - -| Input | Default | Description | -| --- | --- | --- | -| `version` | `v0.1.0-rc.3` | Release tag, `latest`, or `source` (build the action's own sources with cargo; Rust 1.94) | -| `install-dir` | `$RUNNER_TOOL_CACHE/openshell-annotate` | Install location | -| `verify-attestation` | `false` | `gh attestation verify` (needs GitHub CLI) | -| `cache` | `true` | Cache the binary between runs | +| [`setup`](../actions/setup/action.yml) | Install the CLI (release download or `source` build) and add it to `PATH` | +| [`check`](../actions/check/action.yml) | `setup` + `drift` + `lint` in one step (the common PR gate) | +| [`drift`](../actions/drift/action.yml) | Fail when the committed policy no longer matches the directives | +| [`lint`](../actions/lint/action.yml) | Lint directives and call sites; annotations plus a job summary | +| [`generate`](../actions/generate/action.yml) | Regenerate the policy (and providers); optionally upload them as an artifact | +| [`diff-logs`](../actions/diff-logs/action.yml) | Compare sandbox logs with the directives and report gaps | + +`drift`, `lint`, `generate` and `diff-logs` expect `openshell-annotate` on `PATH`, so run `setup` first in the same job. +All paths (`policy`, `providers`, `policy-out`, `logs`, …) are relative to `root`, because the steps run with `root` as +the working directory. + +The tables below are generated from each `action.yml` by `scripts/gen-actions-docs.sh`; do not edit them by hand. + +### `setup` + +Downloads a release with [`install.sh`](../install.sh) (checksum-verified, retried on network errors) and caches it, or +builds from source. + + + +| Input | Required | Default | Description | +| --- | --- | --- | --- | +| `version` | no | `v0.1.0-rc.3` | Release tag to install (e.g. v0.1.0-rc.3), `latest` (newest stable release only), or `source` to build the openshell-annotate sources at the action's own ref with cargo (Rust 1.94+). | +| `install-dir` | no | _(empty)_ | Directory for the openshell-annotate binary (added to PATH); empty means `$RUNNER_TOOL_CACHE/openshell-annotate`. | +| `verify-attestation` | no | `false` | Run `gh attestation verify` after download (needs GitHub CLI + contents:read). | +| `cache` | no | `true` | Cache the installed binary between runs (keyed on OS, arch and `version`; not used for `source`). | | Output | Description | | --- | --- | -| `version` | `openshell-annotate --version` | -| `install-dir` | Directory on `PATH` | - -### `actions/drift` - -| Input | Required | Description | -| --- | --- | --- | -| `policy` | yes | Committed `policy.yaml` (relative to `root`) | -| `root` | no | Repository root (default `.`) | -| `providers` | no | Optional providers JSON | -| `profiles-dir` | no | `--profiles-dir` override | -| `strict` | no | `--strict` | -| `paths` | no | Space-separated scan paths | - -### `actions/lint` - -| Input | Default | Description | -| --- | --- | --- | -| `root` | `.` | Repository root | -| `policy` | _(empty)_ | Policy file for sanity checks | -| `paths` | _(empty)_ | Space-separated paths | -| `failure-threshold` | `error` | `error`, `warning` (`--deny-warnings`), or `never` | - -Findings use GitHub workflow commands (`::error` / `::warning` / `::notice` with `file` and `line`) and a Markdown job -summary table. - -### `actions/generate` - -| Input | Required | Description | -| --- | --- | --- | -| `policy-out` | yes | `-o` path (relative to `root`) | -| `root` | no | Repository root | -| `providers-out` | no | `--providers-out` | -| `profiles-dir` | no | `--profiles-dir` | -| `scaffold-profiles` | no | `--scaffold-profiles` | -| `upload-artifact` | `false` | Upload policy (+ providers) | -| `artifact-name` | `openshell-policy` | Artifact name | +| `version` | Installed openshell-annotate version string (from `openshell-annotate --version`). | +| `install-dir` | Directory containing the `openshell-annotate` binary. | + + + +### `check` + +Runs `setup`, then `drift`, then `lint`, with the same behaviour as the separate actions. It always caches release +downloads and has no `paths` input; use the separate actions when you need those. + + + +| Input | Required | Default | Description | +| --- | --- | --- | --- | +| `version` | no | `v0.1.0-rc.3` | CLI version, as for `setup` (a release tag, `latest` for the newest stable release, or `source` to build the action's own ref). | +| `root` | no | `.` | Repository root for drift and lint. | +| `policy` | yes | — | Committed policy.yaml. | +| `providers` | no | _(empty)_ | Optional committed providers JSON for drift. | +| `profiles-dir` | no | _(empty)_ | Provider profiles directory for drift (`--profiles-dir`). | +| `failure-threshold` | no | `error` | Lint failure threshold (`error`, `warning`, or `never`). | +| `strict-drift` | no | `false` | Pass `--strict` to drift. | + +| Output | Description | +| --- | --- | +| `version` | Installed openshell-annotate version. | + + + +### `drift` + +Runs `openshell-annotate drift --json` and writes the findings (change, section, item, detail) to the job summary. Exits +non-zero on drift; fix it by regenerating the policy and committing it. + + + +| Input | Required | Default | Description | +| --- | --- | --- | --- | +| `root` | no | `.` | Repository root passed to openshell-annotate (`--root`). | +| `policy` | yes | — | Committed policy.yaml (`--policy`). | +| `providers` | no | _(empty)_ | Optional committed providers JSON (`--providers`). | +| `profiles-dir` | no | _(empty)_ | Provider profiles directory (`--profiles-dir`), when not set in config. | +| `strict` | no | `false` | Treat unmanaged committed sections as drift (`--strict`). | +| `paths` | no | _(empty)_ | Space-separated paths to scan (default entire root). | + + + +### `lint` + +Runs `openshell-annotate lint --format json` and turns every finding into a `::error`, `::warning` or `::notice` +annotation with its file and line, plus a summary table by severity. + +| `failure-threshold` | The step fails on | +| --- | --- | +| `error` (default) | errors | +| `warning` | errors or warnings (passes `--deny-warnings`) | +| `never` | nothing; findings are still annotated | + + + +| Input | Required | Default | Description | +| --- | --- | --- | --- | +| `root` | no | `.` | Repository root (`--root`). | +| `policy` | no | _(empty)_ | Policy file to sanity-check (`--policy`); leave empty to skip policy checks. | +| `paths` | no | _(empty)_ | Space-separated paths to lint (default entire root). | +| `failure-threshold` | no | `error` | When to fail the step — `error` (default), `warning` (also fail on warnings), or `never`. | + + + +### `generate` + +Runs `openshell-annotate generate`, creating output directories as needed. + + + +| Input | Required | Default | Description | +| --- | --- | --- | --- | +| `root` | no | `.` | Repository root (`--root`). | +| `policy-out` | yes | — | Output path for policy.yaml (`-o`). | +| `providers-out` | no | _(empty)_ | Output path for providers JSON or shell script (`--providers-out`). | +| `profiles-dir` | no | _(empty)_ | Provider profiles directory (`--profiles-dir`). | +| `scaffold-profiles` | no | _(empty)_ | Directory for `--scaffold-profiles` (creates missing provider profile stubs). | +| `upload-artifact` | no | `false` | Upload policy and providers outputs as a workflow artifact. | +| `artifact-name` | no | `openshell-policy` | Artifact name when upload-artifact is true. | | Output | Description | | --- | --- | -| `policy-path` | Absolute path to generated policy | -| `providers-path` | Absolute path to providers (if requested) | +| `policy-path` | Absolute path to the generated policy file. | +| `providers-path` | Absolute path to the generated providers file, if any. | + + + +### `diff-logs` -### `actions/diff-logs` +Runs `openshell-annotate diff-logs` on one or more logs (the shorthand from `openshell logs --source sandbox`, +or OCSF JSONL) and copies the report to the job summary. It exits 1 while denied connections have no directive. -| Input | Required | Description | -| --- | --- | --- | -| `logs` | yes | Space-separated log files | -| `root` | no | Repository root | -| `write` | no | `--write` | -| `deny-stale` | no | `--deny-stale` | + -### `actions/check` +| Input | Required | Default | Description | +| --- | --- | --- | --- | +| `root` | no | `.` | Repository root (`--root`). | +| `logs` | yes | — | Space-separated log file paths (shorthand or OCSF JSONL). | +| `write` | no | `false` | Insert suggested directives (`--write`). | +| `deny-stale` | no | `false` | Fail when logs show no traffic for a declared egress (`--deny-stale`). | -Combines `setup`, `drift`, and `lint`. Inputs: `version`, `root`, `policy`, `providers`, `profiles-dir`, -`failure-threshold`, `strict-drift`. Output: `version` (installed CLI). + -The actions share their logic through scripts in [`actions/lib`](../actions/lib), called via -`github.action_path`. `check` does not nest `uses: ./actions/...` steps, because inside a composite action a -relative `uses:` resolves against the caller's workspace and breaks remote consumers (#65). The -`consumer-check` job in `.github/workflows/actions-selftest.yml` runs `check` from a subdirectory checkout to -catch that regression. +## Recipes -## CD: regenerate policy in CI +### PR gate with strict drift and providers + +```yaml + - uses: ilanh/openshell-annotate/actions/setup@v0.1.0-rc.3 + - uses: ilanh/openshell-annotate/actions/drift@v0.1.0-rc.3 + with: + policy: openshell/policy.yaml + providers: openshell/providers.json + profiles-dir: openshell/profiles # only if not set in openshell-annotate.toml + strict: "true" # unmanaged sections in the committed policy count as drift + - uses: ilanh/openshell-annotate/actions/lint@v0.1.0-rc.3 + with: + policy: openshell/policy.yaml + failure-threshold: error # `warning` to also fail on warnings, `never` to only annotate +``` + +For a monorepo, run one job per service with `root: services/api` and the paths relative to it, or limit scanning with +`paths: "src lib"` (`drift` and `lint` only). + +### Regenerate the policy and keep it as an artifact ```yaml - uses: ilanh/openshell-annotate/actions/setup@v0.1.0-rc.3 @@ -131,48 +265,94 @@ catch that regression. with: policy-out: openshell/policy.yaml providers-out: openshell/providers.json - upload-artifact: true + upload-artifact: "true" + artifact-name: openshell-policy + - run: echo "policy at ${{ steps.policy.outputs.policy-path }}" ``` -## Example: Policy Playground (`openspell-python-tester`) +### Build an image with the generated policy -```yaml -permissions: - contents: read +Regenerate right before `docker build` so the image always ships the policy that matches the code, and gate the build on +`drift` so a stale committed policy is caught in review: +```yaml jobs: - policy: + check: runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v4.2.2 - - uses: ilanh/openshell-annotate/actions/check@v0.1.0-rc.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: ilanh/openshell-annotate/actions/check@main with: - root: . + version: source policy: openshell/policy.yaml - providers: openshell/providers.json - failure-threshold: warning + image: + needs: check + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: ilanh/openshell-annotate/actions/setup@main + with: + version: source + - uses: ilanh/openshell-annotate/actions/generate@main + with: + policy-out: openshell/policy.yaml + upload-artifact: "true" + - run: docker build -t my-sandbox:ci . ``` -Split steps (same pins): +### Check a sandbox run's logs + +Capture the logs where the sandbox runs (`openshell logs --source sandbox > sandbox.log`, or the OCSF JSONL), +commit them or pass them in as an artifact, then: ```yaml - uses: ilanh/openshell-annotate/actions/setup@v0.1.0-rc.3 - - uses: ilanh/openshell-annotate/actions/drift@v0.1.0-rc.3 + - uses: ilanh/openshell-annotate/actions/diff-logs@v0.1.0-rc.3 with: - policy: openshell/policy.yaml - providers: openshell/providers.json - - uses: ilanh/openshell-annotate/actions/lint@v0.1.0-rc.3 - with: - policy: openshell/policy.yaml - failure-threshold: warning + logs: logs/sandbox.log logs/sandbox.ocsf.jsonl + deny-stale: "true" # also fail on directives the logs never exercised ``` -## Permissions +Add `continue-on-error: true` to report gaps without failing the job. `write: "true"` inserts suggested directives into +the runner's checkout only; it is meant for local runs, since the workflow does not commit anything. -These actions need `contents: read` only. No secrets. Optional attestation verify uses the GitHub CLI with the -default `GITHUB_TOKEN`. +## Real example -## Self-test +[ilanh/openspell-python-tester](https://github.com/ilanh/openspell-python-tester) (the Policy Playground) runs +[`.github/workflows/policy.yml`](https://github.com/ilanh/openspell-python-tester/blob/main/.github/workflows/policy.yml): +`check@main` with `version: source` and `failure-threshold: error` on every push and pull request (its deny examples +are unannotated on purpose, so lint warns but only errors fail), then a job that regenerates the policy with `generate`, +uploads it as `playground-policy` and builds the sandbox image. -The repository runs [`.github/workflows/actions-selftest.yml`](../.github/workflows/actions-selftest.yml) on changes -to `actions/` (Ubuntu and macOS, including an expected drift failure). +## Troubleshooting + +| Symptom | Cause and fix | +| --- | --- | +| `Can't find 'action.yml', 'action.yaml' or 'Dockerfile' under '…/actions/setup'` when using `check` | `check@v0.1.0-rc.3` and older nest `uses: ./actions/...`, which resolves against your repository ([#65](https://github.com/ilanh/openshell-annotate/issues/65)). Use `setup` + `drift` + `lint` at that tag, or a ref that includes [#66](https://github.com/ilanh/openshell-annotate/pull/66). | +| `could not resolve the latest release (is there a stable release yet? use --version)` | `version: latest` only finds stable releases and there are none yet. Pin a tag such as `v0.1.0-rc.3`. | +| `latest` keeps installing an old release | The cache key contains the literal `version`, so `latest` reuses the first cached binary. Set `cache: "false"` on `setup` (`check` always caches), or pin a tag. | +| Source build fails with a Rust version or edition error | `version: source` uses the runner's `cargo` when present and needs Rust 1.94+; otherwise it installs 1.94 with `rustup`. Update the toolchain on self-hosted runners. | +| `install.sh failed (attempt N/5)` or download errors | Release downloads retry with backoff (and `curl` retries each request). If all attempts fail, GitHub was unavailable; re-run the failed jobs. | +| `lint-report.sh requires jq` or an empty drift summary | Install `jq` on self-hosted runners. | +| `Policy drift detected` | Regenerate locally (`openshell-annotate generate -o openshell/policy.yaml`, plus `--providers-out` if you use providers), review and commit. | +| Lint passes locally but fails in CI | CI uses `failure-threshold: warning` → `--deny-warnings`; run `openshell-annotate lint --deny-warnings`. Also check that the CI `version` matches your local CLI. | + +## How the actions are built and tested + +The actions share their logic through scripts in [`actions/lib`](../actions/lib), called via `github.action_path`, so +they work from any repository and checkout path. `check` does not nest `uses: ./actions/...` steps, because inside a +composite action a relative `uses:` resolves against the caller's workspace. + +[`.github/workflows/actions-selftest.yml`](../.github/workflows/actions-selftest.yml) runs on changes to `actions/`, +`install.sh` and these docs: a release install, `check`/`generate`/`diff-logs` on Ubuntu and macOS from source, an +expected drift failure, and a `consumer-check` job that runs `check` the way another repository does (from a +subdirectory checkout, for `source` and a release) to catch #65 regressions. + +### Changing an action + +1. Edit `actions//action.yml` (and `actions/lib/*.sh`). +2. Run `scripts/gen-actions-docs.sh` to refresh the tables above, and update the prose and recipes on this page. + CI runs `scripts/gen-actions-docs.sh --check` and fails when an input or output is missing, renamed or stale here, + or when an action has no section. +3. Note user-visible changes under `## [Unreleased]` in [CHANGELOG.md](../CHANGELOG.md). +4. A new release moves the default `version` and the tags in these docs (see [RELEASING.md](RELEASING.md)). diff --git a/scripts/ci-local.sh b/scripts/ci-local.sh index af9362b..3283b26 100755 --- a/scripts/ci-local.sh +++ b/scripts/ci-local.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Run the same checks as .github/workflows/ci.yml (the `check` job) locally. -# scripts/ci-local.sh # fmt, clippy, test, docs check +# scripts/ci-local.sh # fmt, clippy, test, docs checks # TOOLCHAIN=1.94 scripts/ci-local.sh # pin the toolchain CI uses (needs `rustup toolchain install 1.94`) set -euo pipefail cd "$(dirname "$0")/.." @@ -12,4 +12,5 @@ run cargo $TC clippy --all-targets --locked -- -D warnings run cargo $TC test --locked run cargo $TC test --locked --test gateway_policies run scripts/gen-docs.sh --check +run scripts/gen-actions-docs.sh --check echo; echo "ci-local: all checks passed" diff --git a/scripts/gen-actions-docs.sh b/scripts/gen-actions-docs.sh new file mode 100755 index 0000000..3295fb9 --- /dev/null +++ b/scripts/gen-actions-docs.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +# Regenerate the inputs/outputs tables in docs/GITHUB_ACTIONS.md from actions/*/action.yml. +# scripts/gen-actions-docs.sh # rewrite the tables in place +# scripts/gen-actions-docs.sh --check # fail if the tables are stale or an action has no section (CI) +# +# Each action gets one generated block in the doc, between +# and +# Everything outside the markers is hand-written. The parser only understands the plain action.yml layout used +# in this repository: top-level `inputs:`/`outputs:`, two-space item names, four-space `description:`, +# `required:` and `default:` keys (single-line values or a folded `>` description). +set -euo pipefail +cd "$(dirname "$0")/.." + +DOC="docs/GITHUB_ACTIONS.md" +check=false +case "${1:-}" in + --check) check=true ;; + "") ;; + *) echo "usage: $0 [--check]" >&2; exit 2 ;; +esac + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +# Print the Markdown tables for one action.yml. +tables() { + awk -v q="'" ' + function unquote(v) { + sub(/^[ \t]+/, "", v); sub(/[ \t]+$/, "", v) + if (v ~ /^".*"$/ || v ~ ("^" q ".*" q "$")) v = substr(v, 2, length(v) - 2) + return v + } + function esc(v) { gsub(/\|/, "\\|", v); return v } + function flush() { + if (name == "") return + if (sec == "inputs") { + d = (def != "") ? "`" def "`" : ((req == "true") ? "—" : "_(empty)_") + in_rows[++n_in] = "| `" name "` | " (req == "true" ? "yes" : "no") " | " d " | " esc(desc) " |" + } else { + out_rows[++n_out] = "| `" name "` | " esc(desc) " |" + } + name = "" + } + /^[^ #]/ { + flush(); folding = 0 + sec = ($0 ~ /^inputs:[ \t]*$/) ? "inputs" : (($0 ~ /^outputs:[ \t]*$/) ? "outputs" : "") + next + } + sec == "" { next } + /^ [A-Za-z0-9_-]+:[ \t]*$/ { + flush(); folding = 0 + name = $0; sub(/^ /, "", name); sub(/:.*$/, "", name) + desc = ""; req = "false"; def = "" + next + } + name != "" && /^ [a-z-]+:/ { + folding = 0 + key = $0; sub(/^ /, "", key); sub(/:.*$/, "", key) + val = $0; sub(/^ [a-z-]+:/, "", val); val = unquote(val) + if (key == "description") { + if (val == ">" || val == ">-" || val == "|" || val == "|-") { desc = ""; folding = 1 } else desc = val + } else if (key == "required") req = val + else if (key == "default") def = val + next + } + folding && /^ / { + line = $0; sub(/^[ \t]+/, "", line); sub(/[ \t]+$/, "", line) + desc = (desc == "") ? line : desc " " line + next + } + END { + flush() + if (n_in > 0) { + print "| Input | Required | Default | Description |" + print "| --- | --- | --- | --- |" + for (i = 1; i <= n_in; i++) print in_rows[i] + } else print "_No inputs._" + if (n_out > 0) { + print "" + print "| Output | Description |" + print "| --- | --- |" + for (i = 1; i <= n_out; i++) print out_rows[i] + } + } + ' "$1" +} + +names=() +for yml in actions/*/action.yml; do + name="$(basename "$(dirname "$yml")")" + names+=("$name") + tables "$yml" >"$tmp/$name.md" +done + +status=0 +for name in "${names[@]}"; do + if ! grep -qx "" "$DOC" || ! grep -qx "" "$DOC"; then + echo "error: $DOC has no generated section for actions/$name; add" >&2 + echo " " >&2 + echo " " >&2 + status=1 + fi +done +while IFS= read -r name; do + if [ ! -f "actions/$name/action.yml" ]; then + echo "error: $DOC has a generated section for actions/$name, which does not exist" >&2 + status=1 + fi +done < <(sed -n 's/^$/\1/p' "$DOC") +[ "$status" -eq 0 ] || exit 1 + +awk -v dir="$tmp" ' + /^$/ { + print + name = $0; sub(/^$/, "", name) + print "" + file = dir "/" name ".md" + while ((getline line < file) > 0) print line + close(file) + print "" + skipping = 1 + next + } + /^$/ { skipping = 0 } + !skipping { print } +' "$DOC" >"$tmp/doc.md" + +if $check; then + if ! diff -u "$DOC" "$tmp/doc.md"; then + echo "error: $DOC is out of date with actions/*/action.yml; run scripts/gen-actions-docs.sh and commit it" >&2 + exit 1 + fi + echo "actions docs are up to date" +else + cp "$tmp/doc.md" "$DOC" + echo "updated $DOC" +fi