allow all members in an org to restart/cancel/trigger builds

This commit is contained in:
Jörg Thalheim 2023-12-04 19:44:13 +01:00 committed by mergify[bot]
parent ecf6d6eace
commit dd6eacc4c4
2 changed files with 89 additions and 10 deletions

View file

@ -21,7 +21,9 @@ from buildbot.process.properties import Interpolate, Properties
from buildbot.process.results import ALL_RESULTS, statusToString
from buildbot.steps.trigger import Trigger
from buildbot.util import asyncSleep
from buildbot.www.authz.endpointmatchers import EndpointMatcherBase, Match
from twisted.internet import defer, threads
from twisted.logger import Logger
from twisted.python.failure import Failure
from .github_projects import (
@ -34,6 +36,8 @@ from .github_projects import (
SKIPPED_BUILDER_NAME = "skipped-builds"
log = Logger()
class BuildTrigger(Trigger):
"""
@ -546,6 +550,7 @@ def read_secret_file(secret_name: str) -> str:
class GithubConfig:
oauth_id: str
admins: list[str]
buildbot_user: str
oauth_secret_name: str = "github-oauth-secret"
webhook_secret_name: str = "github-webhook-secret"
@ -654,6 +659,83 @@ def config_for_project(
)
class AnyProjectEndpointMatcher(EndpointMatcherBase):
def __init__(self, builders: set[str] = set(), **kwargs: Any) -> None:
self.builders = builders
super().__init__(**kwargs)
@defer.inlineCallbacks
def check_builder(
self, endpoint_object: Any, endpoint_dict: dict[str, Any], object_type: str
) -> Generator[Any, Any, Any]:
res = yield endpoint_object.get({}, endpoint_dict)
if res is None:
return None
builder = yield self.master.data.get(("builders", res["builderid"]))
if builder["name"] in self.builders:
log.warn(
"Builder {builder} allowed by {role}: {builders}",
builder=builder["name"],
role=self.role,
builders=self.builders,
)
return Match(self.master, **{object_type: res})
else:
log.warn(
"Builder {builder} not allowed by {role}: {builders}",
builder=builder["name"],
role=self.role,
builders=self.builders,
)
def match_BuildEndpoint_rebuild( # noqa: N802
self, epobject: Any, epdict: dict[str, Any], options: dict[str, Any]
) -> Generator[Any, Any, Any]:
return self.check_builder(epobject, epdict, "build")
def match_BuildEndpoint_stop( # noqa: N802
self, epobject: Any, epdict: dict[str, Any], options: dict[str, Any]
) -> Generator[Any, Any, Any]:
return self.check_builder(epobject, epdict, "build")
def match_BuildRequestEndpoint_stop( # noqa: N802
self, epobject: Any, epdict: dict[str, Any], options: dict[str, Any]
) -> Generator[Any, Any, Any]:
return self.check_builder(epobject, epdict, "buildrequest")
def setup_authz(projects: list[GithubProject], admins: list[str]) -> util.Authz:
allow_rules = []
allowed_builders_by_org: defaultdict[str, set[str]] = defaultdict(
lambda: {"reload-github-projects"}
)
for project in projects:
if project.belongs_to_org:
for builder in ["nix-build", "nix-skipped-build", "nix-eval"]:
allowed_builders_by_org[project.owner].add(f"{project.name}/{builder}")
for org, allowed_builders in allowed_builders_by_org.items():
allow_rules.append(
AnyProjectEndpointMatcher(
builders=allowed_builders,
role=org,
defaultDeny=False,
),
)
allow_rules.append(util.AnyEndpointMatcher(role="admin", defaultDeny=False))
allow_rules.append(util.AnyControlEndpointMatcher(role="admins"))
return util.Authz(
roleMatchers=[
util.RolesFromUsername(roles=["admin"], usernames=admins),
util.RolesFromGroups(groupPrefix=""), # so we can match on ORG
],
allowRules=allow_rules,
)
class NixConfigurator(ConfiguratorBase):
"""Janitor is a configurator which create a Janitor Builder with all needed Janitor steps"""
@ -779,14 +861,7 @@ class NixConfigurator(ConfiguratorBase):
config["www"]["auth"] = util.GitHubAuth(
self.github.oauth_id, read_secret_file(self.github.oauth_secret_name)
)
config["www"]["authz"] = util.Authz(
roleMatchers=[
util.RolesFromUsername(
roles=["admin"], usernames=self.github.admins
)
],
allowRules=[
util.AnyEndpointMatcher(role="admin", defaultDeny=False),
util.AnyControlEndpointMatcher(role="admins"),
],
config["www"]["authz"] = setup_authz(
admins=self.github.admins, projects=projects
)

View file

@ -105,6 +105,10 @@ class GithubProject:
def topics(self) -> list[str]:
return self.data["topics"]
@property
def belongs_to_org(self) -> bool:
return self.data["owner"]["type"] == "Organization"
def create_project_hook(
owner: str, repo: str, token: str, webhook_url: str, webhook_secret: str