Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .github/workflows/required-compliance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,19 @@ jobs:
PR_COMMENTS_URL: ${{ github.event.pull_request.comments_url }}
CENTRAL_ORG: ${{ vars.CENTRAL_ORG }}

# Paths for Python to find your scripts & data
# Paths for Python to find your scripts.
#
# Only `scripts` is sparse-checked-out above, so nothing here may
# point into another directory. There was a LICENSES_JSON pointing at
# .github-tools/data/licenses_all.json, which never existed on the
# runner. It was inert only by luck: policy_selector fetches both
# catalogues over the API and passes them to requires_cla in memory,
# and license_detector reads LICENSES_JSON from disk only when it is
# handed no catalog_data. Any future call that omits catalog_data
# would have hit a missing file in production, where this workflow is
# pinned to refs/heads/main and cannot be tested pre-merge.
TOOLS_PATH: ${{ github.workspace }}/.github-tools
PYTHONPATH: ${{ github.workspace }}/.github-tools/scripts
LICENSES_JSON: ${{ github.workspace }}/.github-tools/data/licenses_all.json

# Documentation Links (Used in the failure comment)
CLA_DOC_URL: "https://${{ vars.CENTRAL_ORG }}.github.io/oss-public-policy/Broadcom_CLA"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ on:
- 'tests/**'
- 'cla/**'
- 'data/**'
- '.github/workflows/tests.yml'
- '.github/workflows/**'
push:
branches: [main]
paths:
- 'scripts/**'
- 'tests/**'
- 'cla/**'
- 'data/**'
- '.github/workflows/tests.yml'
- '.github/workflows/**'

permissions:
contents: read
Expand Down
24 changes: 14 additions & 10 deletions cla/allowlist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@
#
# Two further top-level keys are honoured but undocumented elsewhere, so they
# are recorded here rather than left to be rediscovered: a top-level `repos:`
# block (used only when license_overrides.repos is absent or empty), and a
# top-level `repositories:` list whose entries are appended straight to the
# DCO-only set. Prefer license_overrides.repos; the other two exist for
# backwards compatibility.
# block, and a top-level `repositories:` list whose entries are appended
# straight to the DCO-only set. Prefer license_overrides.repos; the other two
# exist for backwards compatibility.
#
# The top-level `repos:` block is a FALLBACK, not a supplement: it is read only
# when license_overrides.repos is absent or empty. Because that block is
# currently populated, anything added to a top-level `repos:` here would be
# silently ignored. fetch_shared_config logs a warning when both are present.
#
# Only these keys do anything. Anything else — including plausible-sounding
# knobs like `spdx_aliases` or a per-repo `force_spdx` — is read by no code,
# so adding one is a silent no-op. tests/test_policy_selector.py asserts this
# for live keys AND for commented-out examples, so a well-meaning suggestion
# in a comment cannot quietly become folklore.
#
# Who skips the gate entirely is decided in policy_selector.process_single_pr(),
# not here. Three paths return early, in order: an existing successful
Expand Down Expand Up @@ -53,11 +63,5 @@ license_overrides:
repos:
vmware/.github:
require_cla: false # false = downgrade this repo to DCO-only
# force_spdx: "MIT" # pretend this is the detected license (rarely needed)
# vmware/docs-site:
# require_cla: false # false = downgrade that repo to DCO-only

# (Optional) Normalize odd/legacy license IDs to canonical SPDX before evaluation.
# The left side is what the detector finds; the right side is the canonical ID.
# spdx_aliases:
# "LicenseRef-BSA": "LicenseRef-Broadcom_Source_Available"
45 changes: 41 additions & 4 deletions scripts/policy_selector.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,13 +486,50 @@ def fetch_shared_config(api_root, gh_token):
f"allowlist must be a mapping, got {type(parsed).__name__}"
)
allowlist_data = parsed
# Handle nesting under 'license_overrides' -> 'repos'
repos_config = allowlist_data.get("license_overrides", {}).get("repos", {})
if not repos_config:
repos_config = allowlist_data.get("repos", {})
# Handle nesting under 'license_overrides' -> 'repos', falling back
# to a top-level 'repos' for backwards compatibility.
#
# `or {}` rather than a .get() default on both lookups: a key that is
# PRESENT but null (`license_overrides:` with nothing under it) yields
# None, and the default only applies when the key is absent. The old
# `.get("license_overrides", {}).get(...)` therefore raised
# AttributeError on that file, which the except below swallowed into
# "enforce CLA everywhere" — the whole allowlist lost to one empty key.
nested_repos = (allowlist_data.get("license_overrides") or {}).get("repos") or {}
legacy_repos = allowlist_data.get("repos") or {}
repos_config = nested_repos or legacy_repos

# The fallback is unreachable whenever the nested block has entries,
# so a top-level 'repos' added alongside one silently does nothing.
# Say so rather than letting someone conclude their entry is live.
if nested_repos and legacy_repos:
# `legacy_repos` is whatever YAML produced and may be a list,
# an int, or a string. sorted() raises TypeError on a list of
# dicts, and the except below would turn that into "enforce CLA
# everywhere" — building a diagnostic must never be the thing
# that discards the policy.
ignored = sorted(legacy_repos) if isinstance(legacy_repos, dict) else repr(legacy_repos)
debug_log(
"⚠️ Top-level 'repos:' is ignored because license_overrides.repos "
"is non-empty. Move those entries under license_overrides.repos; "
f"currently ignored: {ignored}"
)

if isinstance(repos_config, dict):
for r_name, r_config in repos_config.items():
# One malformed entry must cost that entry, not the file.
# `r_config.get(...)` on a null or boolean value raised
# AttributeError, which the except below swallowed into
# enforcing CLA across every gated repo — org-wide blast
# radius from a single missing indent. Skipping instead
# leaves this repo on CLA (absence from the DCO-only list
# is the strict direction) and keeps the rest intact.
if not isinstance(r_config, dict):
debug_log(
f"⚠️ Ignoring repos[{r_name!r}]: expected a mapping, got "
f"{type(r_config).__name__}. That repo stays on CLA."
)
continue
if r_config.get("require_cla") is False:
allowlist_repos.append(r_name)

Expand Down
Loading
Loading