From d80b4a6e4500c38a3a9b4434b3db67327e4f2d3c Mon Sep 17 00:00:00 2001 From: dovholuknf <46322585+dovholuknf@users.noreply.github.com> Date: Mon, 18 May 2026 08:50:53 -0400 Subject: [PATCH] add manual flow for adding cla members --- .github/workflows/add-cla-signature.yml | 59 ++++++++++++ README.md | 4 +- SETUP.md | 34 +++++++ scripts/add-cla-signature.sh | 115 ++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/add-cla-signature.yml create mode 100644 scripts/add-cla-signature.sh diff --git a/.github/workflows/add-cla-signature.yml b/.github/workflows/add-cla-signature.yml new file mode 100644 index 0000000..15e7574 --- /dev/null +++ b/.github/workflows/add-cla-signature.yml @@ -0,0 +1,59 @@ +# Manually record a CLA signature for a contributor who signed out-of-band +# (emailed us a signed PDF, etc.) and has no PR sign-comment to record. +# +# Run from the Actions tab: "Record offline CLA signature" -> Run workflow. + +name: "Record offline CLA signature" + +on: + workflow_dispatch: + inputs: + username: + description: "GitHub username (with or without @)" + required: true + type: string + reason: + description: "Justification, e.g. 'Signed ICLA emailed 2026-05-17, Acme Corp'" + required: true + type: string + cla_version: + description: "CLA version directory (default v1.1)" + required: false + type: string + default: "v1.1" + +permissions: + contents: read + +jobs: + record: + runs-on: ubuntu-latest + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.CLA_APP_ID }} + private-key: ${{ secrets.CLA_APP_PRIVATE_KEY }} + owner: netfoundry + repositories: cla + + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ steps.app-token.outputs.token }} + fetch-depth: 0 + + - name: Configure bot identity + run: | + git config user.email "nf-cla-bot[bot]@users.noreply.github.com" + git config user.name "nf-cla-bot[bot]" + + - name: Append signature and push + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + bash scripts/add-cla-signature.sh \ + --username "${{ inputs.username }}" \ + --reason "${{ inputs.reason }}" \ + --cla-version "${{ inputs.cla_version }}" diff --git a/README.md b/README.md index b3ec4fe..d8a3d43 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,9 @@ If you're contributing on behalf of your employer, your company needs to sign a 3. Email the signed agreement to **cla@netfoundry.io** 4. Include GitHub usernames of employees authorized to contribute -We'll add your team to our allowlist within a few business days. +We'll add your team to our allowlist within a few business days. Maintainers record offline +signatures using the [Record offline CLA signature](./SETUP.md#recording-offline-signatures) +workflow, which commits an entry to the signature ledger as the CLA bot. ## Repositories Covered diff --git a/SETUP.md b/SETUP.md index 1388ad6..4f5e8ad 100644 --- a/SETUP.md +++ b/SETUP.md @@ -138,6 +138,40 @@ To add additional users (e.g., contractors not in the org), either: - Add them to the allowlist in `cla-workflow.yml` - Or add them directly to the current version's `cla.json` +## Recording Offline Signatures + +When a contributor (typically a corporate contributor) signs the CLA out-of-band -- emails us a +signed PDF, signs on paper, etc. -- they have no PR comment for the bot to record. Use the +**Record offline CLA signature** workflow in this repo to add them to the ledger: + +1. Go to the Actions tab in `netfoundry/cla`. +2. Select "Record offline CLA signature" -> "Run workflow". +3. Fill in: + - **username** -- the contributor's GitHub handle (with or without `@`). + - **reason** -- short justification, e.g. `Signed ICLA emailed 2026-05-17, Acme Corp`. This is + stored in the JSON entry as a `note` field and in the commit message, so future maintainers + can answer "why is this person in the ledger without a sign comment?". + - **cla_version** -- defaults to `v1.1`; change when a new version is current. +4. Run. The workflow resolves the user's numeric GitHub id, appends an entry to + `/cla.json`, and commits to `main` as `nf-cla-bot[bot]`. + +The workflow refuses to add a duplicate (it matches by numeric id, so a later username change does +not bypass the check) and fails loudly if the GitHub user does not exist. + +### Running locally + +All logic lives in `scripts/add-cla-signature.sh`, so you can preview a change before pushing: + +```bash +GH_TOKEN=$(gh auth token) bash scripts/add-cla-signature.sh \ + --username someone \ + --reason "Signed ICLA emailed 2026-05-17, Acme Corp" \ + --no-push +``` + +`--no-push` leaves the commit local. Drop the flag (and use a token with write access to +`netfoundry/cla`) to push directly. + ## CLA Documents The workflows link to the official NetFoundry CLA PDFs: diff --git a/scripts/add-cla-signature.sh b/scripts/add-cla-signature.sh new file mode 100644 index 0000000..495810c --- /dev/null +++ b/scripts/add-cla-signature.sh @@ -0,0 +1,115 @@ +#!/usr/bin/env bash +# Manually record a CLA signature for a contributor who signed out-of-band +# (e.g. emailed us a signed PDF) and therefore has no signing comment on a PR. +# +# Required env: +# GH_TOKEN -- a token with contents:write on netfoundry/cla. +# In CI, this is the nf-cla-bot App token. +# Locally, a PAT with `repo` scope works. +# +# Required args: +# --username GitHub handle (with or without leading @) +# --reason Free-text justification, recorded in JSON + commit +# +# Optional args: +# --cla-version Defaults to v1.1 +# --repo-root Defaults to current directory +# --no-push Skip the git push (for local testing) +# +# Exits non-zero on any failure (user not found, already signed, etc.). + +set -euo pipefail + +USERNAME="" +REASON="" +CLA_VERSION="v1.1" +REPO_ROOT="$(pwd)" +DO_PUSH=1 + +while [[ $# -gt 0 ]]; do + case "$1" in + --username) USERNAME="$2"; shift 2 ;; + --reason) REASON="$2"; shift 2 ;; + --cla-version) CLA_VERSION="$2"; shift 2 ;; + --repo-root) REPO_ROOT="$2"; shift 2 ;; + --no-push) DO_PUSH=0; shift ;; + *) echo "Unknown argument: $1" >&2; exit 2 ;; + esac +done + +USERNAME="${USERNAME#@}" + +if [[ -z "$USERNAME" || -z "$REASON" ]]; then + echo "ERROR: --username and --reason are required" >&2 + exit 2 +fi + +if [[ -z "${GH_TOKEN:-}" ]]; then + echo "ERROR: GH_TOKEN env var is required" >&2 + exit 2 +fi + +LEDGER="$REPO_ROOT/$CLA_VERSION/cla.json" +if [[ ! -f "$LEDGER" ]]; then + echo "ERROR: ledger not found at $LEDGER" >&2 + exit 2 +fi + +echo "Resolving GitHub user: $USERNAME" +USER_JSON=$(gh api "users/$USERNAME") +USER_ID=$(echo "$USER_JSON" | jq -r '.id') +RESOLVED_LOGIN=$(echo "$USER_JSON" | jq -r '.login') + +if [[ -z "$USER_ID" || "$USER_ID" == "null" ]]; then + echo "ERROR: could not resolve user id for $USERNAME" >&2 + exit 1 +fi + +echo "Resolved $USERNAME -> login=$RESOLVED_LOGIN id=$USER_ID" + +EXISTING=$(jq --argjson id "$USER_ID" '.signedContributors[] | select(.id == $id) | .name' "$LEDGER") +if [[ -n "$EXISTING" ]]; then + echo "ERROR: user id $USER_ID ($EXISTING) already in $LEDGER -- nothing to do" >&2 + exit 1 +fi + +NOW=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + +TMP=$(mktemp) +jq \ + --arg name "$RESOLVED_LOGIN" \ + --argjson id "$USER_ID" \ + --arg created_at "$NOW" \ + --arg note "$REASON" \ + '.signedContributors += [{ + name: $name, + id: $id, + comment_id: 0, + created_at: $created_at, + repoId: 0, + pullRequestNo: 0, + note: $note + }]' "$LEDGER" > "$TMP" +mv "$TMP" "$LEDGER" + +echo "Appended entry for $RESOLVED_LOGIN ($USER_ID) to $LEDGER" + +cd "$REPO_ROOT" + +# Configure committer identity only if not already set (CI sets these). +if [[ -z "$(git config user.email || true)" ]]; then + git config user.email "nf-cla-bot@users.noreply.github.com" + git config user.name "nf-cla-bot" +fi + +git add "$CLA_VERSION/cla.json" +git commit -m "Manually record CLA signature for @$RESOLVED_LOGIN + +$REASON" + +if [[ "$DO_PUSH" -eq 1 ]]; then + git push origin HEAD:main + echo "Pushed signature commit to main." +else + echo "Skipped push (--no-push). Commit is local." +fi