From 4d6e4c0436615a2994a84a3283da40350356f103 Mon Sep 17 00:00:00 2001 From: "John E. Malmberg" Date: Tue, 2 Jun 2026 16:06:01 -0500 Subject: [PATCH] SRE-3607 Add DCO check to consumer repositories Install the signed-off-by / DCO check in the consumer repositories - README.md: - docs/integration.md: Document usage change. - bin/setup-github-workflow.sh: - checks/workflow_d/dco-signoff.template.yml: Scripts to install the DCO check in the consumer repository Signed-off-by: John E. Malmberg --- README.md | 6 +- bin/setup-github-workflow.sh | 72 +++++++++++++--------- checks/workflow_d/dco-signoff.template.yml | 51 +++++++++++++++ docs/integration.md | 10 +-- 4 files changed, 103 insertions(+), 36 deletions(-) create mode 100644 checks/workflow_d/dco-signoff.template.yml diff --git a/README.md b/README.md index 9c6c8cf..f388aca 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ pwsh -File .\code_checking\bin\sync-consumer.ps1 ``` This command syncs the `code_checking` ref, writes the recommended GitHub -workflow (`pull_request` trigger only, to avoid duplicate `push` + PR runs), +workflows (`pull_request` trigger only, to avoid duplicate `push` + PR runs), bootstraps or refreshes pre-commit hooks, and updates the consumer `README.md` managed section. It also seeds baseline `.gitignore`, `cspell.config.yaml`, `.yamllint`, and `vscode-project-words.txt` in the @@ -187,7 +187,7 @@ Do not stage `code-checking-ref` for normal integration commits. An intentional validation PR may track it temporarily when testing a `code_checking` PR ref. -To install or update only the recommended GitHub workflow without a full +To install or update only the recommended GitHub workflows without a full submodule sync, run: Linux/macOS: @@ -203,7 +203,7 @@ bash .\code_checking\bin\setup-github-workflow.sh --apply ``` This keeps workflow ownership in the consumer repo while providing a shared -script to sync the recommended workflow after submodule add/update. +script to sync the recommended workflows after submodule add/update. Shared linter entrypoints are available at `bin/run-linters.sh` and `bin/run-linters.ps1`. diff --git a/bin/setup-github-workflow.sh b/bin/setup-github-workflow.sh index d9622e6..f62b369 100755 --- a/bin/setup-github-workflow.sh +++ b/bin/setup-github-workflow.sh @@ -4,7 +4,6 @@ set -euo pipefail TARGET_ROOT="$(pwd)" SUBMODULE_PATH="code_checking" -WORKFLOW_RELATIVE_PATH=".github/workflows/basic-source-checks.yml" MODE="check" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -14,8 +13,9 @@ escape_sed_replacement() { render_workflow_yaml() { local code_checking_path="$1" + local template_name="$2" local template_path="${SCRIPT_DIR}/../checks/workflow_d/" - template_path+="basic-source-checks.template.yml" + template_path+="${template_name}" if [[ ! -f "${template_path}" ]]; then printf '%s\n' "[setup-github-workflow] missing template:" \ @@ -34,15 +34,14 @@ usage() { Usage: setup-github-workflow.sh [--target-root PATH] \ [--submodule-path PATH] [--apply] -Checks or writes the recommended GitHub workflow that runs shared linters from -this repository when used as a submodule. +Checks or writes the recommended GitHub workflows for consumer repositories. Defaults: - target root: current directory - submodule path: code_checking - mode: check (non-mutating) -Use --apply to write/update the workflow file. +Use --apply to write/update workflow files. EOF } @@ -73,31 +72,48 @@ while [[ $# -gt 0 ]]; do done TARGET_ROOT="$(cd "${TARGET_ROOT}" && pwd)" -WORKFLOW_PATH="${TARGET_ROOT}/${WORKFLOW_RELATIVE_PATH}" -TMP_EXPECTED="$(mktemp)" -trap 'rm -f "${TMP_EXPECTED}"' EXIT -render_workflow_yaml "${SUBMODULE_PATH}" > "${TMP_EXPECTED}" -if [[ "${MODE}" == "check" ]]; then - if [[ ! -f "${WORKFLOW_PATH}" ]]; then - printf '%s\n' "[setup-github-workflow] missing workflow:" \ - "${WORKFLOW_RELATIVE_PATH}" >&2 - printf '%s\n' "[setup-github-workflow] run with --apply to create it" >&2 - exit 1 - fi +WORKFLOW_TARGETS=( + ".github/workflows/basic-source-checks.yml:basic-source-checks.template.yml" + ".github/workflows/dco-signoff.yml:dco-signoff.template.yml" +) + +for workflow_target in "${WORKFLOW_TARGETS[@]}"; do + workflow_relative_path="${workflow_target%%:*}" + template_name="${workflow_target#*:}" + workflow_path="${TARGET_ROOT}/${workflow_relative_path}" + tmp_expected="$(mktemp)" + + render_workflow_yaml "${SUBMODULE_PATH}" "${template_name}" > "${tmp_expected}" + + if [[ "${MODE}" == "check" ]]; then + if [[ ! -f "${workflow_path}" ]]; then + printf '%s\n' "[setup-github-workflow] missing workflow:" \ + "${workflow_relative_path}" >&2 + printf '%s\n' "[setup-github-workflow] run with --apply to create it" >&2 + rm -f "${tmp_expected}" + exit 1 + fi - if ! cmp -s "${WORKFLOW_PATH}" "${TMP_EXPECTED}"; then - printf '%s\n' "[setup-github-workflow] workflow differs from" \ - "recommended content" >&2 - printf '%s\n' "[setup-github-workflow] file: ${WORKFLOW_RELATIVE_PATH}" >&2 - printf '%s\n' "[setup-github-workflow] run with --apply to update it" >&2 - exit 1 + if ! cmp -s "${workflow_path}" "${tmp_expected}"; then + printf '%s\n' "[setup-github-workflow] workflow differs from" \ + "recommended content" >&2 + printf '%s\n' "[setup-github-workflow] file: ${workflow_relative_path}" >&2 + printf '%s\n' "[setup-github-workflow] run with --apply to update it" >&2 + rm -f "${tmp_expected}" + exit 1 + fi + + rm -f "${tmp_expected}" + continue fi - printf '%s\n' "[setup-github-workflow] workflow is up to date" - exit 0 -fi + mkdir -p "$(dirname "${workflow_path}")" + cp "${tmp_expected}" "${workflow_path}" + rm -f "${tmp_expected}" + printf '%s\n' "[setup-github-workflow] wrote ${workflow_relative_path}" +done -mkdir -p "$(dirname "${WORKFLOW_PATH}")" -cp "${TMP_EXPECTED}" "${WORKFLOW_PATH}" -printf '%s\n' "[setup-github-workflow] wrote ${WORKFLOW_RELATIVE_PATH}" +if [[ "${MODE}" == "check" ]]; then + printf '%s\n' "[setup-github-workflow] workflows are up to date" +fi diff --git a/checks/workflow_d/dco-signoff.template.yml b/checks/workflow_d/dco-signoff.template.yml new file mode 100644 index 0000000..5122eca --- /dev/null +++ b/checks/workflow_d/dco-signoff.template.yml @@ -0,0 +1,51 @@ +--- +name: dco-signoff + +'on': + pull_request: + +permissions: + contents: read + +jobs: + dco-signoff-check: + name: DCO / Signed-off-by + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Verify Signed-off-by trailers + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -euo pipefail + + mapfile -t commits < <(git rev-list --reverse "${BASE_SHA}..${HEAD_SHA}") + + if [[ ${#commits[@]} -eq 0 ]]; then + echo "[dco] no commits found in PR range" + exit 0 + fi + + missing=0 + for commit in "${commits[@]}"; do + trailers="$(git log -1 --pretty=%B "${commit}" | git interpret-trailers --parse)" + if ! grep -qi '^signed-off-by:' <<< "${trailers}"; then + subject="$(git log -1 --pretty=%s "${commit}")" + echo "::error title=Missing Signed-off-by::${commit}: ${subject}" + missing=1 + fi + done + + if [[ ${missing} -ne 0 ]]; then + echo "[dco] one or more commits are missing Signed-off-by trailers" >&2 + echo "[dco] amend commits with: git commit --amend --signoff" >&2 + echo "[dco] for multiple commits: git rebase -i --signoff " >&2 + exit 1 + fi + + echo "[dco] all commits include Signed-off-by trailers" diff --git a/docs/integration.md b/docs/integration.md index 1dc9b19..7fed2ac 100644 --- a/docs/integration.md +++ b/docs/integration.md @@ -41,7 +41,7 @@ pwsh -File .\code_checking\bin\sync-consumer.ps1 -SkipReadme See [README.md](../README.md) for details on what `sync-consumer` does. -Optionally, you can manually sync or validate the recommended GitHub workflow +Optionally, you can manually sync or validate the recommended GitHub workflows (see [README.md](../README.md) for details on `setup-github-workflow.sh`). ## Initial Consumer Commit @@ -117,17 +117,17 @@ commits; you need to fetch and hard-reset. commit it. Keep the submodule pointer at the commit already recorded in the consumer PR. -1. If the code_checking PR changed linter/tool requirements, regenerate the - consumer workflow. +1. If the code_checking PR changed linter/tool requirements or branch policy + checks, regenerate consumer workflows. ```bash ./code_checking/bin/setup-github-workflow.sh --apply ``` -1. Stage and commit only the changed workflow file. +1. Stage and commit only changed workflow files. ```bash - git add .github/workflows/basic-source-checks.yml + git add .github/workflows/ git commit --amend --no-verify ```