Skip to content

Commit 1d57aab

Browse files
committed
Add users option to both GitHub and Gitea restricting project owners
Signed-off-by: magic_rb <richard@brezak.sk>
1 parent ccaecb0 commit 1d57aab

File tree

5 files changed

+106
-12
lines changed

5 files changed

+106
-12
lines changed

buildbot_nix/buildbot_nix/common.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,29 @@ def atomic_write_file(file: Path, data: str) -> None:
114114
Y = TypeVar("Y")
115115

116116

117-
def filter_repos_by_topic(
118-
topic: str | None, repos: list[Y], topics: Callable[[Y], list[str]]
117+
def filter_repos(
118+
repo_allowlist: list[str] | None,
119+
user_allowlist: list[str] | None,
120+
topic: str | None,
121+
repos: list[Y],
122+
repo_name: Callable[[Y], str],
123+
user: Callable[[Y], str],
124+
topics: Callable[[Y], list[str]],
119125
) -> list[Y]:
126+
# This is a bit complicated so let me explain:
127+
# If both `user_allowlist` and `repo_allowlist` are `None` then we want to allow everything,
128+
# however if either are non-`None`, then the one that is non-`None` determines whether to
129+
# allow a repo, or both if both are non-+None`.
130+
120131
return list(
121132
filter(
122-
lambda repo: topic is None or topic in topics(repo),
123-
repos,
133+
lambda repo: (user_allowlist is None and repo_allowlist is None)
134+
or (user_allowlist is not None and user(repo) in user_allowlist)
135+
or (repo_allowlist is not None and repo_name(repo) in repo_allowlist),
136+
filter(
137+
lambda repo: topic is None or topic in topics(repo),
138+
repos,
139+
),
124140
)
125141
)
126142

buildbot_nix/buildbot_nix/gitea_projects.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from .common import (
2121
ThreadDeferredBuildStep,
2222
atomic_write_file,
23-
filter_repos_by_topic,
23+
filter_repos,
2424
http_request,
2525
model_dump_project_cache,
2626
model_validate_project_cache,
@@ -175,12 +175,16 @@ def load_projects(self) -> list["GitProject"]:
175175
if not self.config.project_cache_file.exists():
176176
return []
177177

178-
repos: list[RepoData] = filter_repos_by_topic(
178+
repos: list[RepoData] = filter_repos(
179+
self.config.repo_allowlist,
180+
self.config.user_allowlist,
179181
self.config.topic,
180182
sorted(
181183
model_validate_project_cache(RepoData, self.config.project_cache_file),
182184
key=lambda repo: repo.full_name,
183185
),
186+
lambda repo: repo.full_name,
187+
lambda repo: repo.owner.login,
184188
lambda repo: repo.topics,
185189
)
186190
repo_names: list[str] = [repo.owner.login + "/" + repo.name for repo in repos]
@@ -310,9 +314,13 @@ def __init__(
310314
super().__init__(**kwargs)
311315

312316
def run_deferred(self) -> None:
313-
repos: list[RepoData] = filter_repos_by_topic(
317+
repos: list[RepoData] = filter_repos(
318+
self.config.repo_allowlist,
319+
self.config.user_allowlist,
314320
self.config.topic,
315321
refresh_projects(self.config, self.project_cache_file),
322+
lambda repo: repo.full_name,
323+
lambda repo: repo.owner.login,
316324
lambda repo: repo.topics,
317325
)
318326

buildbot_nix/buildbot_nix/github_projects.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from .common import (
2626
ThreadDeferredBuildStep,
2727
atomic_write_file,
28-
filter_repos_by_topic,
28+
filter_repos,
2929
http_request,
3030
model_dump_project_cache,
3131
model_validate_project_cache,
@@ -145,6 +145,8 @@ class ReloadGithubInstallations(ThreadDeferredBuildStep):
145145
installation_token_map_name: Path
146146
project_id_map_name: Path
147147
topic: str | None
148+
user_allowlist: list[str] | None
149+
repo_allowlist: list[str] | None
148150

149151
def __init__(
150152
self,
@@ -153,13 +155,17 @@ def __init__(
153155
installation_token_map_name: Path,
154156
project_id_map_name: Path,
155157
topic: str | None,
158+
user_allowlist: list[str] | None,
159+
repo_allowlist: list[str] | None,
156160
**kwargs: Any,
157161
) -> None:
158162
self.jwt_token = jwt_token
159163
self.installation_token_map_name = installation_token_map_name
160164
self.project_id_map_name = project_id_map_name
161165
self.project_cache_file = project_cache_file
162166
self.topic = topic
167+
self.user_allowlist = user_allowlist
168+
self.repo_allowlist = repo_allowlist
163169
super().__init__(**kwargs)
164170

165171
def run_deferred(self) -> None:
@@ -179,7 +185,9 @@ def run_deferred(self) -> None:
179185
repos = []
180186

181187
for k, v in installation_token_map.items():
182-
new_repos = filter_repos_by_topic(
188+
new_repos = filter_repos(
189+
self.repo_allowlist,
190+
self.user_allowlist,
183191
self.topic,
184192
refresh_projects(
185193
v.get(),
@@ -189,6 +197,8 @@ def run_deferred(self) -> None:
189197
subkey="repositories",
190198
require_admin=False,
191199
),
200+
lambda repo: repo.full_name,
201+
lambda repo: repo.owner.login,
192202
lambda repo: repo.topics,
193203
)
194204

@@ -259,23 +269,33 @@ class ReloadGithubProjects(ThreadDeferredBuildStep):
259269
token: RepoToken
260270
project_cache_file: Path
261271
topic: str | None
272+
user_allowlist: list[str] | None
273+
repo_allowlist: list[str] | None
262274

263275
def __init__(
264276
self,
265277
token: RepoToken,
266278
project_cache_file: Path,
267279
topic: str | None,
280+
user_allowlist: list[str] | None,
281+
repo_allowlist: list[str] | None,
268282
**kwargs: Any,
269283
) -> None:
270284
self.token = token
271285
self.project_cache_file = project_cache_file
272286
self.topic = topic
287+
self.user_allowlist = user_allowlist
288+
self.repo_allowlist = repo_allowlist
273289
super().__init__(**kwargs)
274290

275291
def run_deferred(self) -> None:
276-
repos: list[RepoData] = filter_repos_by_topic(
292+
repos: list[RepoData] = filter_repos(
293+
self.repo_allowlist,
294+
self.user_allowlist,
277295
self.topic,
278296
refresh_projects(self.token.get(), self.project_cache_file),
297+
lambda repo: repo.full_name,
298+
lambda repo: repo.owner.login,
279299
lambda repo: repo.topics,
280300
)
281301

@@ -309,6 +329,8 @@ def create_reload_builder_steps(
309329
webhook_secret: str,
310330
webhook_url: str,
311331
topic: str | None,
332+
user_allowlist: list[str] | None,
333+
repo_allowlist: list[str] | None,
312334
) -> list[BuildStep]:
313335
pass
314336

@@ -349,12 +371,16 @@ def create_reload_builder_steps(
349371
webhook_secret: str,
350372
webhook_url: str,
351373
topic: str | None,
374+
user_allowlist: list[str] | None,
375+
repo_allowlist: list[str] | None,
352376
) -> list[BuildStep]:
353377
return [
354378
ReloadGithubProjects(
355379
token=self.token,
356380
project_cache_file=project_cache_file,
357381
topic=topic,
382+
user_allowlist=user_allowlist,
383+
repo_allowlist=repo_allowlist,
358384
),
359385
CreateGitHubProjectHooks(
360386
token=self.token,
@@ -443,14 +469,18 @@ def create_reload_builder_steps(
443469
webhook_secret: str,
444470
webhook_url: str,
445471
topic: str | None,
472+
user_allowlist: list[str] | None,
473+
repo_allowlist: list[str] | None,
446474
) -> list[BuildStep]:
447475
return [
448476
ReloadGithubInstallations(
449477
self.jwt_token,
450478
project_cache_file,
451479
self.auth_type.installation_token_map_file,
452480
self.auth_type.project_id_map_file,
453-
topic,
481+
topic=topic,
482+
user_allowlist=user_allowlist,
483+
repo_allowlist=repo_allowlist,
454484
),
455485
CreateGitHubInstallationHooks(
456486
self.jwt_token,
@@ -550,6 +580,8 @@ def create_reload_builder(self, worker_names: list[str]) -> BuilderConfig:
550580
self.webhook_secret,
551581
self.webhook_url,
552582
self.config.topic,
583+
self.config.user_allowlist,
584+
self.config.repo_allowlist,
553585
)
554586
for step in steps:
555587
factory.addStep(step)
@@ -596,12 +628,16 @@ def load_projects(self) -> list["GitProject"]:
596628
if not self.config.project_cache_file.exists():
597629
return []
598630

599-
repos: list[RepoData] = filter_repos_by_topic(
631+
repos: list[RepoData] = filter_repos(
632+
self.config.repo_allowlist,
633+
self.config.user_allowlist,
600634
self.config.topic,
601635
sorted(
602636
model_validate_project_cache(RepoData, self.config.project_cache_file),
603637
key=lambda repo: repo.full_name,
604638
),
639+
lambda repo: repo.full_name,
640+
lambda repo: repo.owner.login,
605641
lambda repo: repo.topics,
606642
)
607643

buildbot_nix/buildbot_nix/models.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ class GiteaConfig(BaseModel):
9696
instance_url: str
9797
topic: str | None
9898

99+
user_allowlist: list[str] | None
100+
repo_allowlist: list[str] | None
101+
99102
token_file: Path = Field(default=Path("gitea-token"))
100103
webhook_secret_file: Path = Field(default=Path("gitea-webhook-secret"))
101104
project_cache_file: Path = Field(default=Path("gitea-project-cache.json"))
@@ -183,6 +186,8 @@ class GitHubConfig(BaseModel):
183186
auth_type: GitHubLegacyConfig | GitHubAppConfig
184187
topic: str | None
185188

189+
user_allowlist: list[str] | None
190+
repo_allowlist: list[str] | None
186191
project_cache_file: Path = Field(default=Path("github-project-cache-v1.json"))
187192
webhook_secret_file: Path = Field(default=Path("github-webhook-secret"))
188193

nix/master.nix

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,18 @@ in
337337
default = cfg.authBackend == "gitea";
338338
};
339339

340+
userAllowlist = lib.mkOption {
341+
type = lib.types.nullOr (lib.types.listOf lib.types.str);
342+
default = null;
343+
description = "If non-null, specifies users/organizations that are allowed to use buildbot, i.e. buildbot-nix will ignore any repositories not owned by these users/organizations.";
344+
};
345+
346+
repoAllowlist = lib.mkOption {
347+
type = lib.types.nullOr (lib.types.listOf lib.types.str);
348+
default = null;
349+
description = "If non-null, specifies an explicit set of repositories that are allowed to use buildbot, i.e. buildbot-nix will ignore any repositories not in this list.";
350+
};
351+
340352
tokenFile = lib.mkOption {
341353
type = lib.types.path;
342354
description = "Gitea token file";
@@ -374,6 +386,18 @@ in
374386
default = cfg.authBackend == "github";
375387
};
376388

389+
userAllowlist = lib.mkOption {
390+
type = lib.types.nullOr (lib.types.listOf lib.types.str);
391+
default = null;
392+
description = "If non-null, specifies users/organizations that are allowed to use buildbot, i.e. buildbot-nix will ignore any repositories not owned by these users/organizations.";
393+
};
394+
395+
repoAllowlist = lib.mkOption {
396+
type = lib.types.nullOr (lib.types.listOf lib.types.str);
397+
default = null;
398+
description = "If non-null, specifies an explicit set of repositories that are allowed to use buildbot, i.e. buildbot-nix will ignore any repositories not in this list.";
399+
};
400+
377401
authType = lib.mkOption {
378402
type = lib.types.attrTag {
379403
legacy = lib.mkOption {
@@ -530,6 +554,7 @@ in
530554
default = [ ];
531555
description = "Users that are allowed to login to buildbot, trigger builds and change settings";
532556
};
557+
533558
workersFile = lib.mkOption {
534559
type = lib.types.path;
535560
description = "File containing a list of nix workers";
@@ -794,6 +819,8 @@ in
794819
null
795820
else
796821
{
822+
user_allowlist = cfg.gitea.userAllowlist;
823+
repo_allowlist = cfg.gitea.repoAllowlist;
797824
token_file = "gitea-token";
798825
webhook_secret_file = "gitea-webhook-secret";
799826
project_cache_file = "gitea-project-cache.json";
@@ -807,6 +834,8 @@ in
807834
null
808835
else
809836
{
837+
user_allowlist = cfg.github.userAllowlist;
838+
repo_allowlist = cfg.github.repoAllowlist;
810839
auth_type =
811840
if (cfg.github.authType ? "legacy") then
812841
{ token_file = "github-token"; }

0 commit comments

Comments
 (0)