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 6a39e3071..8d68bbdb5 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]: @@ -130,10 +131,24 @@ 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 + + # 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 a 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"]) 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",