From efbcf6468c085f77ce8682fd9d4cef4736878c57 Mon Sep 17 00:00:00 2001 From: Stephan Merker Date: Tue, 18 Nov 2025 10:57:28 +0100 Subject: [PATCH 1/3] WG repo org validation - rfc-0036-multiple-github-orgs: Working Groups MUST only contain repos from one CFF Github Org - repos from unmanaged orgs are allowed as temporary exception (they are just documentation) --- orgs/org_management/org_management.py | 17 ++++++++++++++++- orgs/org_management/test_org_management.py | 18 +++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/orgs/org_management/org_management.py b/orgs/org_management/org_management.py index 6a39e3071..1c86e3879 100644 --- a/orgs/org_management/org_management.py +++ b/orgs/org_management/org_management.py @@ -120,9 +120,10 @@ def load_from_project(self): raise ValueError(f"Invalid org {org} in WG {wg['name']}, expected one of {OrgGenerator._MANAGED_ORGS}") self.working_groups[org].append(wg) - # rfc-0007-repository-ownership: a repo can't be owned by multiple WGs, scope is github org def validate_repo_ownership(self) -> bool: valid = True + + # rfc-0007-repository-ownership: a repo can't be owned by multiple WGs, scope is github org for org in OrgGenerator._MANAGED_ORGS: repo_owners = {} for wg in self.working_groups[org]: @@ -134,6 +135,20 @@ def validate_repo_ownership(self) -> bool: valid = False else: repo_owners[repo] = wg_name + + # rfc-0036-multiple-github-orgs: Working Groups MUST only contain repos from one CFF Github Org (but repos from unmanaged orgs are allowed as temporary exception) + for org in self.working_groups.keys(): + for wg in self.working_groups[org]: + wg_name = wg["name"] + wg_repos = set(r for a in wg["areas"] for r in a["repositories"]) + for repo in wg_repos: + repo_org = repo.split("/")[0] + if repo_org != org and repo_org in OrgGenerator._MANAGED_ORGS: + print( + f"ERROR: Working Group {wg_name} assigned to Github org {org} contains repository {repo} from different managed org." + ) + valid = False + return valid def get_contributors(self, org: str) -> set[str]: diff --git a/orgs/org_management/test_org_management.py b/orgs/org_management/test_org_management.py index 74d8469f4..173d73d31 100644 --- a/orgs/org_management/test_org_management.py +++ b/orgs/org_management/test_org_management.py @@ -208,7 +208,7 @@ repositories: - cloudfoundry2/repo3 - cloudfoundry2/repo4 - - cloudfoundry/repo5 + - cloudfoundry2/repo5 """ toc = """ @@ -474,6 +474,18 @@ def test_validate_repo_ownership(self): o = OrgGenerator(static_org_cfg=org_cfg, toc=toc, working_groups=[wg1, wg2, wg3]) self.assertFalse(o.validate_repo_ownership()) + def test_validate_repo_ownership_multiple_orgs(self): + OrgGenerator._MANAGED_ORGS = ["cloudfoundry", "cloudfoundry2"] + o = OrgGenerator(static_org_cfg=org_cfg_multiple, toc=toc, working_groups=[wg1, wg4_other_org]) + self.assertTrue(o.validate_repo_ownership()) + # includes non-managed orgs + o = OrgGenerator(static_org_cfg=org_cfg_multiple, toc=toc, working_groups=[wg1, wg2, wg4_other_org]) + self.assertTrue(o.validate_repo_ownership()) + # wg can only have repos of one org + bad_wg4_other_org = wg4_other_org.replace("cloudfoundry2/repo5", "cloudfoundry/repo5") + o = OrgGenerator(static_org_cfg=org_cfg_multiple, toc=toc, working_groups=[wg1, bad_wg4_other_org]) + self.assertFalse(o.validate_repo_ownership()) + def test_generate_wg_teams(self): _wg1 = OrgGenerator._yaml_load(wg1) OrgGenerator._validate_wg(_wg1) @@ -559,7 +571,7 @@ def test_generate_wg_teams_multiple_orgs(self): self.assertDictEqual({"repo1": "write", "repo2": "write"}, team["repos"]) team = wg_team["teams"]["wg-wg4-name-area-2-approvers"] - self.assertDictEqual({"repo3": "write", "repo4": "write"}, team["repos"]) + self.assertDictEqual({"repo3": "write", "repo4": "write", "repo5": "write"}, team["repos"]) def test_generate_toc_team(self): _toc = OrgGenerator._yaml_load(toc) @@ -758,7 +770,7 @@ def test_generate_branch_protection_multiple_orgs(self): bp_repos = o.branch_protection["branch-protection"]["orgs"]["cloudfoundry2"]["repos"] # wg4 opted in, repo5 is ignored because of wrong org - self.assertSetEqual({f"repo{i}" for i in range(1, 5)}, set(bp_repos.keys())) + self.assertSetEqual({f"repo{i}" for i in range(1, 6)}, set(bp_repos.keys())) # repo1 has static config that wins over generated branch protection rules self.assertTrue(bp_repos["repo1"]["protect"]) self.assertNotIn("required_pull_request_reviews", bp_repos["repo1"]) From 14bdc7f72439deb2bc117a2d5570d84ff3d81149 Mon Sep 17 00:00:00 2001 From: Stephan Merker Date: Tue, 18 Nov 2025 11:25:06 +0100 Subject: [PATCH 2/3] Fix license --- orgs/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/orgs/pyproject.toml b/orgs/pyproject.toml index 923260e4d..c4b95c2a8 100644 --- a/orgs/pyproject.toml +++ b/orgs/pyproject.toml @@ -4,7 +4,7 @@ version = "0.1.0" description = "Automation for GitHub orgs managed by the Cloud Foundry Foundation" readme = "readme.md" requires-python = ">=3.14" -license-files = ["LICENSE"] +license = "Apache-2.0" classifiers = ["Private :: Do Not Upload"] dependencies = [ "pyyaml", From 88093b71680aed16fbdfff39e67c72490fdaad63 Mon Sep 17 00:00:00 2001 From: Stephan Merker Date: Tue, 18 Nov 2025 12:41:26 +0100 Subject: [PATCH 3/3] Minor err msg improvements --- orgs/org_management/__main__.py | 2 +- orgs/org_management/org_management.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/orgs/org_management/__main__.py b/orgs/org_management/__main__.py index 1573d143e..8648f7945 100644 --- a/orgs/org_management/__main__.py +++ b/orgs/org_management/__main__.py @@ -14,7 +14,7 @@ generator = OrgGenerator() generator.load_from_project() if not generator.validate_repo_ownership(): - print("ERROR: Repository ownership is invalid. Refer to RFC-0007.") + print("ERROR: Repository ownership is invalid. Refer to RFC-0007 and RFC-0036.") exit(1) generator.generate_org_members() generator.generate_teams() diff --git a/orgs/org_management/org_management.py b/orgs/org_management/org_management.py index 1c86e3879..8d68bbdb5 100644 --- a/orgs/org_management/org_management.py +++ b/orgs/org_management/org_management.py @@ -131,7 +131,7 @@ def validate_repo_ownership(self) -> bool: wg_repos = set(r for a in wg["areas"] for r in a["repositories"]) for repo in wg_repos: if repo in repo_owners: - print(f"ERROR: Repository {repo} is owned by multiple WGs: {repo_owners[repo]}, {wg_name}") + print(f"ERROR: Repository '{repo}' is owned by multiple WGs: {repo_owners[repo]}, {wg_name}") valid = False else: repo_owners[repo] = wg_name @@ -145,7 +145,7 @@ def validate_repo_ownership(self) -> bool: repo_org = repo.split("/")[0] if repo_org != org and repo_org in OrgGenerator._MANAGED_ORGS: print( - f"ERROR: Working Group {wg_name} assigned to Github org {org} contains repository {repo} from different managed org." + f"ERROR: Working Group '{wg_name}' assigned to Github org '{org}' contains repository '{repo}' from a different managed org." ) valid = False