also create webhooks automatically
This commit is contained in:
parent
bf1fed375f
commit
54bcb08fae
161
.gitignore
vendored
161
.gitignore
vendored
|
@ -1 +1,162 @@
|
||||||
result
|
result
|
||||||
|
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
|
|
@ -19,7 +19,11 @@ from buildbot.process.project import Project
|
||||||
from buildbot.process.properties import Interpolate, Properties
|
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 github_projects import GithubProject, load_projects # noqa: E402
|
from github_projects import ( # noqa: E402
|
||||||
|
GithubProject,
|
||||||
|
create_project_hook,
|
||||||
|
load_projects,
|
||||||
|
)
|
||||||
from twisted.internet import defer
|
from twisted.internet import defer
|
||||||
|
|
||||||
|
|
||||||
|
@ -511,7 +515,7 @@ class GithubConfig:
|
||||||
webhook_secret_name: str = "github-webhook-secret"
|
webhook_secret_name: str = "github-webhook-secret"
|
||||||
token_secret_name: str = "github-token"
|
token_secret_name: str = "github-token"
|
||||||
project_cache_file: Path = Path("github-project-cache.json")
|
project_cache_file: Path = Path("github-project-cache.json")
|
||||||
topic_filter: str | None = "build-with-buildbot"
|
topic: str | None = "build-with-buildbot"
|
||||||
|
|
||||||
def token(self) -> str:
|
def token(self) -> str:
|
||||||
return read_secret_file(self.token_secret_name)
|
return read_secret_file(self.token_secret_name)
|
||||||
|
@ -621,6 +625,7 @@ class NixConfigurator(ConfiguratorBase):
|
||||||
# Shape of this file:
|
# Shape of this file:
|
||||||
# [ { "name": "<worker-name>", "pass": "<worker-password>", "cores": "<cpu-cores>" } ]
|
# [ { "name": "<worker-name>", "pass": "<worker-password>", "cores": "<cpu-cores>" } ]
|
||||||
github: GithubConfig,
|
github: GithubConfig,
|
||||||
|
url: str,
|
||||||
nix_supported_systems: list[str],
|
nix_supported_systems: list[str],
|
||||||
nix_eval_max_memory_size: int = 4096,
|
nix_eval_max_memory_size: int = 4096,
|
||||||
nix_workers_secret_name: str = "buildbot-nix-workers",
|
nix_workers_secret_name: str = "buildbot-nix-workers",
|
||||||
|
@ -630,12 +635,13 @@ class NixConfigurator(ConfiguratorBase):
|
||||||
self.nix_eval_max_memory_size = nix_eval_max_memory_size
|
self.nix_eval_max_memory_size = nix_eval_max_memory_size
|
||||||
self.nix_supported_systems = nix_supported_systems
|
self.nix_supported_systems = nix_supported_systems
|
||||||
self.github = github
|
self.github = github
|
||||||
|
self.url = url
|
||||||
self.systemd_credentials_dir = os.environ["CREDENTIALS_DIRECTORY"]
|
self.systemd_credentials_dir = os.environ["CREDENTIALS_DIRECTORY"]
|
||||||
|
|
||||||
def configure(self, config: dict[str, Any]) -> None:
|
def configure(self, config: dict[str, Any]) -> None:
|
||||||
projects = load_projects(self.github.token(), self.github.project_cache_file)
|
projects = load_projects(self.github.token(), self.github.project_cache_file)
|
||||||
if self.github.topic_filter is not None:
|
if self.github.topic is not None:
|
||||||
projects = [p for p in projects if self.github.topic_filter in p.topics]
|
projects = [p for p in projects if self.github.topic in p.topics]
|
||||||
worker_config = json.loads(read_secret_file(self.nix_workers_secret_name))
|
worker_config = json.loads(read_secret_file(self.nix_workers_secret_name))
|
||||||
worker_names = []
|
worker_names = []
|
||||||
config["workers"] = config.get("workers", [])
|
config["workers"] = config.get("workers", [])
|
||||||
|
@ -647,6 +653,15 @@ class NixConfigurator(ConfiguratorBase):
|
||||||
worker_names.append(worker_name)
|
worker_names.append(worker_name)
|
||||||
|
|
||||||
config["projects"] = config.get("projects", [])
|
config["projects"] = config.get("projects", [])
|
||||||
|
|
||||||
|
for project in projects:
|
||||||
|
create_project_hook(
|
||||||
|
project.owner,
|
||||||
|
project.repo,
|
||||||
|
self.github.token(),
|
||||||
|
f"{self.url}/change_hook/github",
|
||||||
|
)
|
||||||
|
|
||||||
for project in projects:
|
for project in projects:
|
||||||
config_for_project(
|
config_for_project(
|
||||||
config,
|
config,
|
||||||
|
@ -657,6 +672,7 @@ class NixConfigurator(ConfiguratorBase):
|
||||||
self.nix_supported_systems,
|
self.nix_supported_systems,
|
||||||
self.nix_eval_max_memory_size,
|
self.nix_eval_max_memory_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
config["services"] = config.get("services", [])
|
config["services"] = config.get("services", [])
|
||||||
config["services"].append(
|
config["services"].append(
|
||||||
reporters.GitHubStatusPush(
|
reporters.GitHubStatusPush(
|
||||||
|
|
|
@ -30,18 +30,28 @@ def http_request(
|
||||||
headers = headers.copy()
|
headers = headers.copy()
|
||||||
headers["User-Agent"] = "buildbot-nix"
|
headers["User-Agent"] = "buildbot-nix"
|
||||||
req = urllib.request.Request(url, headers=headers, method=method, data=body)
|
req = urllib.request.Request(url, headers=headers, method=method, data=body)
|
||||||
|
try:
|
||||||
resp = urllib.request.urlopen(req)
|
resp = urllib.request.urlopen(req)
|
||||||
|
except urllib.request.HTTPError as e:
|
||||||
|
body = ""
|
||||||
|
try:
|
||||||
|
body = e.fp.read()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
raise Exception(
|
||||||
|
f"Request for {method} {url} failed with {e.code} {e.reason}: {body}"
|
||||||
|
) from e
|
||||||
return HttpResponse(resp)
|
return HttpResponse(resp)
|
||||||
|
|
||||||
|
|
||||||
def paginated_github_request(url: str, token: str) -> list[dict[str, Any]]:
|
def paginated_github_request(url: str, token: str) -> list[dict[str, Any]]:
|
||||||
next_url: str | None = url
|
next_url: str | None = url
|
||||||
repos = []
|
items = []
|
||||||
while next_url:
|
while next_url:
|
||||||
try:
|
try:
|
||||||
res = http_request(
|
res = http_request(
|
||||||
next_url,
|
next_url,
|
||||||
headers={"Authorization": f"token {token}"},
|
headers={"Authorization": f"Bearer {token}"},
|
||||||
)
|
)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise Exception(f"failed to fetch {next_url}: {e}") from e
|
raise Exception(f"failed to fetch {next_url}: {e}") from e
|
||||||
|
@ -53,34 +63,67 @@ def paginated_github_request(url: str, token: str) -> list[dict[str, Any]]:
|
||||||
link_parts = link.split(";")
|
link_parts = link.split(";")
|
||||||
if link_parts[1].strip() == 'rel="next"':
|
if link_parts[1].strip() == 'rel="next"':
|
||||||
next_url = link_parts[0][1:-1]
|
next_url = link_parts[0][1:-1]
|
||||||
repos += res.json()
|
items += res.json()
|
||||||
return repos
|
return items
|
||||||
|
|
||||||
|
|
||||||
class GithubProject:
|
class GithubProject:
|
||||||
def __init__(self, repo: dict[str, Any]) -> None:
|
def __init__(self, data: dict[str, Any]) -> None:
|
||||||
self.repo = repo
|
self.data = data
|
||||||
|
|
||||||
|
@property
|
||||||
|
def repo(self) -> str:
|
||||||
|
return self.data["name"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def owner(self) -> str:
|
||||||
|
return self.data["owner"]["login"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self) -> str:
|
||||||
return self.repo["full_name"]
|
return self.data["full_name"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def url(self) -> str:
|
def url(self) -> str:
|
||||||
return self.repo["html_url"]
|
return self.data["html_url"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self) -> str:
|
def id(self) -> str:
|
||||||
n = self.repo["full_name"]
|
n = self.data["full_name"]
|
||||||
return n.replace("/", "-")
|
return n.replace("/", "-")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def default_branch(self) -> str:
|
def default_branch(self) -> str:
|
||||||
return self.repo["default_branch"]
|
return self.data["default_branch"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def topics(self) -> list[str]:
|
def topics(self) -> list[str]:
|
||||||
return self.repo["topics"]
|
return self.data["topics"]
|
||||||
|
|
||||||
|
|
||||||
|
def create_project_hook(owner: str, repo: str, token: str, webhook_url: str) -> None:
|
||||||
|
hooks = paginated_github_request(
|
||||||
|
f"https://api.github.com/repos/{owner}/{repo}/hooks?per_page=100", token
|
||||||
|
)
|
||||||
|
config = dict(url=webhook_url, content_type="json", insecure_ssl="0")
|
||||||
|
data = dict(name="web", active=True, events=["push", "pull_request"], config=config)
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {token}",
|
||||||
|
"Accept": "application/vnd.github+json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-GitHub-Api-Version": "2022-11-28",
|
||||||
|
}
|
||||||
|
for hook in hooks:
|
||||||
|
if hook["config"]["url"] == webhook_url:
|
||||||
|
log.msg(f"hook for {owner}/{repo} already exists")
|
||||||
|
return
|
||||||
|
|
||||||
|
http_request(
|
||||||
|
f"https://api.github.com/repos/{owner}/{repo}/hooks",
|
||||||
|
method="POST",
|
||||||
|
headers=headers,
|
||||||
|
data=data,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def load_projects(github_token: str, repo_cache_file: Path) -> list[GithubProject]:
|
def load_projects(github_token: str, repo_cache_file: Path) -> list[GithubProject]:
|
||||||
|
|
|
@ -109,6 +109,7 @@ in
|
||||||
buildbot_user=${builtins.toJSON cfg.github.user},
|
buildbot_user=${builtins.toJSON cfg.github.user},
|
||||||
topic=${builtins.toJSON cfg.github.topic},
|
topic=${builtins.toJSON cfg.github.topic},
|
||||||
),
|
),
|
||||||
|
url=${builtins.toJSON config.services.buildbot-master.buildbotUrl},
|
||||||
nix_eval_max_memory_size=${builtins.toJSON cfg.evalMaxMemorySize},
|
nix_eval_max_memory_size=${builtins.toJSON cfg.evalMaxMemorySize},
|
||||||
nix_supported_systems=${builtins.toJSON cfg.buildSystems},
|
nix_supported_systems=${builtins.toJSON cfg.buildSystems},
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue