From 5618300a096929d10a516a45f9b373a6129a67cd Mon Sep 17 00:00:00 2001 From: Fabian Wienand Date: Tue, 28 Jul 2026 14:12:22 +0200 Subject: [PATCH] ci: run firmwareci for fork pull requests Pull requests from forks run without repository secrets, so FWCI_TOKEN is empty and the FirmwareCI step in fwci-test cannot submit a job. Skip that job for fork pull requests and add fwci-fork, which triggers on the completion of fwci-test, downloads the binaries it already built, and submits them from the base repository where the secrets live. Fork-authored code never runs in the new workflow. It checks out this repository, not the contributor's, and only hands the build artifact to FirmwareCI. What still needs a human is the hardware itself, so the job sits behind the firmwareci-fork environment: approving it means letting a contributor's build run on a real DUT. The artifact comes from a run of the contributor's code, so the filename lookup rejects anything but a single plainly named path before it reaches GITHUB_OUTPUT, and event payload values are passed through the environment instead of being interpolated into the script. BRANCH is pinned to main because the fork's branch does not exist here and the FirmwareCI config has to resolve against a branch of this repository. COMMIT_HASH comes from the workflow_run payload, which carries the pull request head commit rather than the base ref this workflow runs on. The firmwareci-fork environment must exist with required reviewers before this has any effect. Signed-off-by: Fabian Wienand --- .github/workflows/fwci-fork.yml | 108 ++++++++++++++++++++++++++++++++ .github/workflows/fwci-test.yml | 6 ++ 2 files changed, 114 insertions(+) create mode 100644 .github/workflows/fwci-fork.yml diff --git a/.github/workflows/fwci-fork.yml b/.github/workflows/fwci-fork.yml new file mode 100644 index 0000000..6bc7170 --- /dev/null +++ b/.github/workflows/fwci-fork.yml @@ -0,0 +1,108 @@ +--- +name: fwci-fork +# Pull requests from forks run without repository secrets, so fwci-test cannot +# submit a FirmwareCI job for them. This workflow runs in the base repository, +# where the secrets live, and submits the binaries the fork's run already built. +# +# Fork-authored code is never executed here: this job only downloads the build +# artifact and hands it to FirmwareCI. The environment gate below is what +# decides whether that firmware is allowed onto real hardware. + +on: + workflow_run: + workflows: ["fwci-test"] + types: [completed] + +permissions: + contents: read + actions: read # required to read the triggering run's artifacts + +concurrency: + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} + cancel-in-progress: true + +jobs: + client-agent-integration: + # Fork pull requests only. Same-repo pull requests and pushes have secrets + # and already submit their job from fwci-test. + if: >- + github.event.workflow_run.event == 'pull_request' && + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_repository.full_name != github.repository + runs-on: ubuntu-latest + # Required reviewers on this environment are the gate. Approving means + # letting a contributor's firmware run on real hardware, so the reviewer + # should have read the diff first. + environment: firmwareci-fork + strategy: + fail-fast: false + matrix: + configfile: + - ./contrib/dutagent-cfg-example.yaml + #- ./test/test-dutagent-cfg.yaml + steps: + # Base repository only — the fork's tree is deliberately not checked out. + # This provides the config file passed as CONFIG below. + - name: Checkout + uses: actions/checkout@v7 + + - uses: actions/download-artifact@v8 + with: + name: binaries + path: bin/ + run-id: ${{ github.event.workflow_run.id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: Get names of binaries + id: filenames + run: | + set -euo pipefail + # This artifact was produced by a run of the contributor's code, so + # its contents are untrusted. Reject anything but a single, plainly + # named path before letting it reach GITHUB_OUTPUT: a filename + # containing a newline would otherwise let the artifact append + # arbitrary step outputs. + check() { + case "$1" in + "" | *[!A-Za-z0-9./_-]*) + echo "refusing unexpected artifact path: $1" >&2 + exit 1 + ;; + esac + } + dutctl_path=$(ls ./bin/dutctl_linux_amd64_*/dutctl) + dutctl_pkg_path=$(ls ./bin/dutctl_*_arm64.deb) + check "$dutctl_path" + check "$dutctl_pkg_path" + echo "dutctl_path=$dutctl_path" >> "${GITHUB_OUTPUT}" + echo "dutctl_pkg_path=$dutctl_pkg_path" >> "${GITHUB_OUTPUT}" + + - name: Debug + # Values from the event payload go through the environment rather than + # being interpolated into the script, so nothing from the fork side can + # be parsed as shell. + env: + SOURCE_REPO: ${{ github.event.workflow_run.head_repository.full_name }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} + DUTCTL_PATH: ${{ steps.filenames.outputs.dutctl_path }} + DUTCTL_PKG_PATH: ${{ steps.filenames.outputs.dutctl_pkg_path }} + run: | + echo "source: ${SOURCE_REPO}" + echo "head_sha: ${HEAD_SHA}" + echo "dutctl_path: ${DUTCTL_PATH}" + echo "dutctl_pkg_path: ${DUTCTL_PKG_PATH}" + + - name: Upload to FirmwareCI + uses: docker://firmwareci/action:v6.0 + with: + TOKEN: "${{ secrets.FWCI_TOKEN }}" + WORKFLOW_NAME: test-dutctl + # The workflow_run payload carries the pull request head commit. This + # workflow itself runs on the base branch, so auto-detection would + # report the wrong commit. + COMMIT_HASH: ${{ github.event.workflow_run.head_sha }} + # The fork's branch does not exist in this repository, so the + # FirmwareCI config has to be resolved against the base branch. + BRANCH: main + BINARIES: DUTCTL=${{ steps.filenames.outputs.dutctl_path }};REMOTE_PKG=${{ steps.filenames.outputs.dutctl_pkg_path }};CONFIG=${{ matrix.configfile }} + GITHUB_INSTALLATION_ID: 45795153 diff --git a/.github/workflows/fwci-test.yml b/.github/workflows/fwci-test.yml index 441cb76..4b448d7 100644 --- a/.github/workflows/fwci-test.yml +++ b/.github/workflows/fwci-test.yml @@ -45,6 +45,12 @@ jobs: # Run a firmware-ci test suite client-agent-integration: + # Pull requests from forks run without repository secrets, so FWCI_TOKEN + # would be empty here. The fwci-fork workflow picks those up instead, from + # the base repository and behind a required-reviewer environment. + if: >- + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest needs: - build-with-goreleaser