allow all members in an org to restart/cancel/trigger builds
This commit is contained in:
parent
ecf6d6eace
commit
dd6eacc4c4
|
@ -21,7 +21,9 @@ from buildbot.process.properties import Interpolate, Properties
|
||||||
from buildbot.process.results import ALL_RESULTS, statusToString
|
from buildbot.process.results import ALL_RESULTS, statusToString
|
||||||
from buildbot.steps.trigger import Trigger
|
from buildbot.steps.trigger import Trigger
|
||||||
from buildbot.util import asyncSleep
|
from buildbot.util import asyncSleep
|
||||||
|
from buildbot.www.authz.endpointmatchers import EndpointMatcherBase, Match
|
||||||
from twisted.internet import defer, threads
|
from twisted.internet import defer, threads
|
||||||
|
from twisted.logger import Logger
|
||||||
from twisted.python.failure import Failure
|
from twisted.python.failure import Failure
|
||||||
|
|
||||||
from .github_projects import (
|
from .github_projects import (
|
||||||
|
@ -34,6 +36,8 @@ from .github_projects import (
|
||||||
|
|
||||||
SKIPPED_BUILDER_NAME = "skipped-builds"
|
SKIPPED_BUILDER_NAME = "skipped-builds"
|
||||||
|
|
||||||
|
log = Logger()
|
||||||
|
|
||||||
|
|
||||||
class BuildTrigger(Trigger):
|
class BuildTrigger(Trigger):
|
||||||
"""
|
"""
|
||||||
|
@ -546,6 +550,7 @@ def read_secret_file(secret_name: str) -> str:
|
||||||
class GithubConfig:
|
class GithubConfig:
|
||||||
oauth_id: str
|
oauth_id: str
|
||||||
admins: list[str]
|
admins: list[str]
|
||||||
|
|
||||||
buildbot_user: str
|
buildbot_user: str
|
||||||
oauth_secret_name: str = "github-oauth-secret"
|
oauth_secret_name: str = "github-oauth-secret"
|
||||||
webhook_secret_name: str = "github-webhook-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):
|
class NixConfigurator(ConfiguratorBase):
|
||||||
"""Janitor is a configurator which create a Janitor Builder with all needed Janitor steps"""
|
"""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(
|
config["www"]["auth"] = util.GitHubAuth(
|
||||||
self.github.oauth_id, read_secret_file(self.github.oauth_secret_name)
|
self.github.oauth_id, read_secret_file(self.github.oauth_secret_name)
|
||||||
)
|
)
|
||||||
config["www"]["authz"] = util.Authz(
|
|
||||||
roleMatchers=[
|
config["www"]["authz"] = setup_authz(
|
||||||
util.RolesFromUsername(
|
admins=self.github.admins, projects=projects
|
||||||
roles=["admin"], usernames=self.github.admins
|
|
||||||
)
|
|
||||||
],
|
|
||||||
allowRules=[
|
|
||||||
util.AnyEndpointMatcher(role="admin", defaultDeny=False),
|
|
||||||
util.AnyControlEndpointMatcher(role="admins"),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -105,6 +105,10 @@ class GithubProject:
|
||||||
def topics(self) -> list[str]:
|
def topics(self) -> list[str]:
|
||||||
return self.data["topics"]
|
return self.data["topics"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def belongs_to_org(self) -> bool:
|
||||||
|
return self.data["owner"]["type"] == "Organization"
|
||||||
|
|
||||||
|
|
||||||
def create_project_hook(
|
def create_project_hook(
|
||||||
owner: str, repo: str, token: str, webhook_url: str, webhook_secret: str
|
owner: str, repo: str, token: str, webhook_url: str, webhook_secret: str
|
||||||
|
|
Loading…
Reference in a new issue