add option to reload github projects

fix mypy
This commit is contained in:
Jörg Thalheim 2023-10-12 23:01:07 +02:00
parent 74fb30f6ff
commit e82d3a3d91
2 changed files with 101 additions and 16 deletions

View file

@ -3,6 +3,7 @@
import json import json
import multiprocessing import multiprocessing
import os import os
import signal
import sys import sys
import uuid import uuid
from collections import defaultdict from collections import defaultdict
@ -23,8 +24,10 @@ from github_projects import ( # noqa: E402
GithubProject, GithubProject,
create_project_hook, create_project_hook,
load_projects, load_projects,
refresh_projects,
) )
from twisted.internet import defer from twisted.internet import defer, threads
from twisted.python.failure import Failure
class BuildTrigger(Trigger): class BuildTrigger(Trigger):
@ -51,10 +54,10 @@ class BuildTrigger(Trigger):
**kwargs, **kwargs,
) )
def createTriggerProperties(self, props: Any) -> Any: def createTriggerProperties(self, props: Any) -> Any: # noqa: N802
return props return props
def getSchedulersAndProperties(self) -> list[tuple[str, Properties]]: def getSchedulersAndProperties(self) -> list[tuple[str, Properties]]: # noqa: N802
build_props = self.build.getProperties() build_props = self.build.getProperties()
repo_name = build_props.getProperty( repo_name = build_props.getProperty(
"github.base.repo.full_name", "github.base.repo.full_name",
@ -94,7 +97,7 @@ class BuildTrigger(Trigger):
triggered_schedulers.append((sch, props)) triggered_schedulers.append((sch, props))
return triggered_schedulers return triggered_schedulers
def getCurrentSummary(self) -> dict[str, str]: def getCurrentSummary(self) -> dict[str, str]: # noqa: N802
""" """
The original build trigger will the generic builder name `nix-build` in this case, which is not helpful The original build trigger will the generic builder name `nix-build` in this case, which is not helpful
""" """
@ -250,6 +253,58 @@ class UpdateBuildOutput(steps.BuildStep):
return util.SUCCESS return util.SUCCESS
class ReloadGithubProjects(steps.BuildStep):
name = "reload_github_projects"
def __init__(self, token: str, project_cache_file: Path, **kwargs: Any) -> None:
self.token = token
self.project_cache_file = project_cache_file
super().__init__(**kwargs)
def reload_projects(self) -> None:
refresh_projects(self.token, self.project_cache_file)
@defer.inlineCallbacks
def run(self) -> Generator[Any, object, Any]:
d = threads.deferToThread(self.reload_projects)
self.error_msg = ""
def error_cb(failure: Failure) -> int:
self.error_msg += failure.getTraceback()
return util.FAILURE
d.addCallbacks(lambda _: util.SUCCESS, error_cb)
res = yield d
if res == util.SUCCESS:
# reload the buildbot config
os.kill(os.getpid(), signal.SIGHUP)
return util.SUCCESS
else:
log: Log = yield self.addLog("log")
log.addStderr(f"Failed to reload project list: {self.error_msg}")
return util.FAILURE
def reload_github_projects(
worker_names: list[str],
github_token_secret: str,
project_cache_file: Path,
) -> util.BuilderConfig:
"""
Updates the flake an opens a PR for it.
"""
factory = util.BuildFactory()
factory.addStep(
ReloadGithubProjects(github_token_secret, project_cache_file=project_cache_file)
)
return util.BuilderConfig(
name="reload-github-projects",
workernames=worker_names,
factory=factory,
)
def nix_update_flake_config( def nix_update_flake_config(
project: GithubProject, project: GithubProject,
worker_names: list[str], worker_names: list[str],
@ -676,6 +731,21 @@ class NixConfigurator(ConfiguratorBase):
self.nix_eval_max_memory_size, self.nix_eval_max_memory_size,
) )
# Reload github projects
config["builders"].append(
reload_github_projects(
[worker_names[0]],
self.github.token(),
self.github.project_cache_file,
)
)
config["schedulers"].append(
schedulers.ForceScheduler(
name="reload-github-projects",
builderNames=["reload-github-projects"],
buttonName="Update projects",
)
)
config["services"] = config.get("services", []) config["services"] = config.get("services", [])
config["services"].append( config["services"].append(
reporters.GitHubStatusPush( reporters.GitHubStatusPush(

View file

@ -1,7 +1,9 @@
import http.client import http.client
import json import json
import os
import urllib.request import urllib.request
from pathlib import Path from pathlib import Path
from tempfile import NamedTemporaryFile
from typing import Any from typing import Any
from twisted.python import log from twisted.python import log
@ -33,13 +35,13 @@ def http_request(
try: try:
resp = urllib.request.urlopen(req) resp = urllib.request.urlopen(req)
except urllib.request.HTTPError as e: except urllib.request.HTTPError as e:
body = "" resp_body = ""
try: try:
body = e.fp.read() resp_body = e.fp.read().decode("utf-8", "replace")
except Exception: except Exception:
pass pass
raise Exception( raise Exception(
f"Request for {method} {url} failed with {e.code} {e.reason}: {body}" f"Request for {method} {url} failed with {e.code} {e.reason}: {resp_body}"
) from e ) from e
return HttpResponse(resp) return HttpResponse(resp)
@ -101,11 +103,15 @@ class GithubProject:
return self.data["topics"] return self.data["topics"]
def create_project_hook(owner: str, repo: str, token: str, webhook_url: str, webhook_secret) -> None: def create_project_hook(
owner: str, repo: str, token: str, webhook_url: str, webhook_secret: str
) -> None:
hooks = paginated_github_request( hooks = paginated_github_request(
f"https://api.github.com/repos/{owner}/{repo}/hooks?per_page=100", token f"https://api.github.com/repos/{owner}/{repo}/hooks?per_page=100", token
) )
config = dict(url=webhook_url, content_type="json", insecure_ssl="0", secret=webhook_secret) config = dict(
url=webhook_url, content_type="json", insecure_ssl="0", secret=webhook_secret
)
data = dict(name="web", active=True, events=["push", "pull_request"], config=config) data = dict(name="web", active=True, events=["push", "pull_request"], config=config)
headers = { headers = {
"Authorization": f"Bearer {token}", "Authorization": f"Bearer {token}",
@ -126,16 +132,25 @@ def create_project_hook(owner: str, repo: str, token: str, webhook_url: str, web
) )
def refresh_projects(github_token: str, repo_cache_file: Path) -> None:
repos = paginated_github_request(
"https://api.github.com/user/repos?per_page=100",
github_token,
)
with NamedTemporaryFile("w", delete=False, dir=repo_cache_file.parent) as f:
try:
f.write(json.dumps(repos))
f.flush()
os.rename(f.name, repo_cache_file)
except OSError:
os.unlink(f.name)
raise
def load_projects(github_token: str, repo_cache_file: Path) -> list[GithubProject]: def load_projects(github_token: str, repo_cache_file: Path) -> list[GithubProject]:
if repo_cache_file.exists(): if repo_cache_file.exists():
log.msg("fetching github repositories from cache") log.msg("fetching github repositories from cache")
repos: list[dict[str, Any]] = json.loads(repo_cache_file.read_text()) repos: list[dict[str, Any]] = json.loads(repo_cache_file.read_text())
else: else:
log.msg("fetching github repositories from api") refresh_projects(github_token, repo_cache_file)
repos = paginated_github_request(
"https://api.github.com/user/repos?per_page=100",
github_token,
)
repo_cache_file.write_text(json.dumps(repos, indent=2))
return [GithubProject(repo) for repo in repos] return [GithubProject(repo) for repo in repos]