2024-07-19 20:20:42 +00:00
|
|
|
from enum import Enum
|
|
|
|
from pathlib import Path
|
|
|
|
|
2024-07-24 15:10:05 +00:00
|
|
|
from buildbot.plugins import steps, util
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
2024-07-19 20:20:42 +00:00
|
|
|
|
|
|
|
from .secrets import read_secret_file
|
|
|
|
|
2024-07-24 15:10:05 +00:00
|
|
|
|
|
|
|
class InternalError(Exception):
|
2024-07-19 20:20:42 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def exclude_fields(fields: list[str]) -> dict[str, dict[str, bool]]:
|
2024-07-24 15:10:05 +00:00
|
|
|
return {k: {"exclude": True} for k in fields}
|
|
|
|
|
2024-07-19 20:20:42 +00:00
|
|
|
|
|
|
|
class AuthBackendConfig(str, Enum):
|
|
|
|
github = "github"
|
|
|
|
gitea = "gitea"
|
|
|
|
none = "none"
|
|
|
|
|
2024-07-24 15:10:05 +00:00
|
|
|
|
2024-07-19 20:20:42 +00:00
|
|
|
class CachixConfig(BaseModel):
|
|
|
|
name: str
|
|
|
|
|
|
|
|
signing_key_file: Path | None
|
|
|
|
auth_token_file: Path | None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def signing_key(self) -> str:
|
|
|
|
if self.signing_key_file is None:
|
2024-07-24 15:10:05 +00:00
|
|
|
raise InternalError
|
2024-07-19 20:20:42 +00:00
|
|
|
return read_secret_file(self.signing_key_file)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def auth_token(self) -> str:
|
|
|
|
if self.auth_token_file is None:
|
2024-07-24 15:10:05 +00:00
|
|
|
raise InternalError
|
2024-07-19 20:20:42 +00:00
|
|
|
return read_secret_file(self.auth_token_file)
|
|
|
|
|
|
|
|
# TODO why did the original implementation return an empty env if both files were missing?
|
|
|
|
@property
|
|
|
|
def environment(self) -> dict[str, str]:
|
|
|
|
environment = {}
|
|
|
|
environment["CACHIX_SIGNING_KEY"] = util.Secret(self.signing_key_file)
|
|
|
|
environment["CACHIX_AUTH_TOKEN"] = util.Secret(self.auth_token_file)
|
|
|
|
return environment
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
fields = exclude_fields(["singing_key", "auth_token"])
|
|
|
|
|
2024-07-24 15:10:05 +00:00
|
|
|
|
2024-07-19 20:20:42 +00:00
|
|
|
class GiteaConfig(BaseModel):
|
|
|
|
instance_url: str
|
|
|
|
topic: str | None
|
|
|
|
|
2024-07-24 15:10:05 +00:00
|
|
|
token_file: Path = Field(default=Path("gitea-token"))
|
|
|
|
webhook_secret_file: Path = Field(default=Path("gitea-webhook-secret"))
|
|
|
|
project_cache_file: Path = Field(default=Path("gitea-project-cache.json"))
|
2024-07-19 20:20:42 +00:00
|
|
|
|
|
|
|
oauth_id: str | None
|
|
|
|
oauth_secret_file: Path | None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def token(self) -> str:
|
|
|
|
return read_secret_file(self.token_file)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def webhook_secret(self) -> str:
|
|
|
|
return read_secret_file(self.webhook_secret_file)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def oauth_secret(self) -> str:
|
|
|
|
if self.oauth_secret_file is None:
|
2024-07-24 15:10:05 +00:00
|
|
|
raise InternalError
|
2024-07-19 20:20:42 +00:00
|
|
|
return read_secret_file(self.oauth_secret_file)
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
fields = exclude_fields(["token", "webhook_secret", "oauth_secret"])
|
|
|
|
|
2024-07-24 15:10:05 +00:00
|
|
|
|
2024-07-19 20:20:42 +00:00
|
|
|
class GitHubLegacyConfig(BaseModel):
|
|
|
|
token_file: Path
|
|
|
|
|
|
|
|
@property
|
|
|
|
def token(self) -> str:
|
|
|
|
return read_secret_file(self.token_file)
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
fields = exclude_fields(["token"])
|
|
|
|
|
2024-07-24 15:10:05 +00:00
|
|
|
|
2024-07-19 20:20:42 +00:00
|
|
|
class GitHubAppConfig(BaseModel):
|
|
|
|
id: int
|
|
|
|
|
|
|
|
secret_key_file: Path
|
2024-07-24 15:10:05 +00:00
|
|
|
installation_token_map_file: Path = Field(
|
|
|
|
default=Path("github-app-installation-token-map.json")
|
|
|
|
)
|
|
|
|
project_id_map_file: Path = Field(
|
|
|
|
default=Path("github-app-project-id-map-name.json")
|
|
|
|
)
|
|
|
|
jwt_token_map: Path = Field(default=Path("github-app-jwt-token"))
|
2024-07-19 20:20:42 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def secret_key(self) -> str:
|
|
|
|
return read_secret_file(self.secret_key_file)
|
|
|
|
|
|
|
|
class Config:
|
|
|
|
fields = exclude_fields(["secret_key"])
|
|
|
|
|
2024-07-24 15:10:05 +00:00
|
|
|
|
2024-07-19 20:20:42 +00:00
|
|
|
class GitHubConfig(BaseModel):
|
|
|
|
auth_type: GitHubLegacyConfig | GitHubAppConfig
|
|
|
|
topic: str | None
|
|
|
|
|
2024-07-24 15:10:05 +00:00
|
|
|
project_cache_file: Path = Field(default=Path("github-project-cache-v1.json"))
|
|
|
|
webhook_secret_file: Path = Field(default=Path("github-webhook-secret"))
|
2024-07-19 20:20:42 +00:00
|
|
|
|
|
|
|
oauth_id: str | None
|
|
|
|
oauth_secret_file: Path | None
|
|
|
|
|
|
|
|
@property
|
|
|
|
def webhook_secret(self) -> str:
|
|
|
|
return read_secret_file(self.webhook_secret_file)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def oauth_secret(self) -> str:
|
|
|
|
if self.oauth_secret_file is None:
|
2024-07-24 15:10:05 +00:00
|
|
|
raise InternalError
|
2024-07-19 20:20:42 +00:00
|
|
|
return read_secret_file(self.oauth_secret_file)
|
|
|
|
|
2024-07-24 15:10:05 +00:00
|
|
|
|
2024-07-19 20:20:42 +00:00
|
|
|
# note that serialization isn't correct, as there is no way to *rename* the field `nix_type` to `_type`,
|
|
|
|
# one must always specify `by_alias = True`, such as `model_dump(by_alias = True)`, relevant issue:
|
|
|
|
# https://github.com/pydantic/pydantic/issues/8379
|
|
|
|
class Interpolate(BaseModel):
|
|
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
|
2024-07-24 15:10:05 +00:00
|
|
|
nix_type: str = Field(alias="_type")
|
2024-07-19 20:20:42 +00:00
|
|
|
value: str
|
|
|
|
|
|
|
|
|
|
|
|
class PostBuildStep(BaseModel):
|
|
|
|
name: str
|
|
|
|
environment: dict[str, str | Interpolate]
|
|
|
|
command: list[str | Interpolate]
|
|
|
|
|
|
|
|
def to_buildstep(self) -> steps.BuildStep:
|
|
|
|
def maybe_interpolate(value: str | Interpolate) -> str | util.Interpolate:
|
|
|
|
if isinstance(value, str):
|
|
|
|
return value
|
|
|
|
return util.Interpolate(value.value)
|
|
|
|
|
|
|
|
return steps.ShellCommand(
|
2024-07-24 15:10:05 +00:00
|
|
|
name=self.name,
|
|
|
|
env={k: maybe_interpolate(k) for k in self.environment},
|
|
|
|
command=[maybe_interpolate(x) for x in self.command],
|
2024-07-19 20:20:42 +00:00
|
|
|
)
|
|
|
|
|
2024-07-24 15:10:05 +00:00
|
|
|
|
2024-07-19 20:20:42 +00:00
|
|
|
class BuildbotNixConfig(BaseModel):
|
|
|
|
db_url: str
|
|
|
|
auth_backend: AuthBackendConfig
|
|
|
|
build_retries: int
|
|
|
|
cachix: CachixConfig | None
|
|
|
|
gitea: GiteaConfig | None
|
|
|
|
github: GitHubConfig | None
|
|
|
|
admins: list[str]
|
|
|
|
workers_file: Path
|
|
|
|
build_systems: list[str]
|
|
|
|
eval_max_memory_size: int
|
|
|
|
eval_worker_count: int | None
|
2024-07-24 15:10:05 +00:00
|
|
|
nix_workers_secret_file: Path = Field(default=Path("buildbot-nix-workers"))
|
2024-07-19 20:20:42 +00:00
|
|
|
domain: str
|
|
|
|
webhook_base_url: str
|
|
|
|
use_https: bool
|
|
|
|
outputs_path: Path | None
|
|
|
|
url: str
|
|
|
|
post_build_steps: list[PostBuildStep]
|
2024-07-12 14:44:02 +00:00
|
|
|
job_report_limit: int | None
|
2024-07-19 20:20:42 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def nix_workers_secret(self) -> str:
|
|
|
|
return read_secret_file(self.nix_workers_secret_file)
|