|
1 | 1 | import os
|
2 | 2 | import signal
|
| 3 | +from collections.abc import Generator |
3 | 4 | from pathlib import Path
|
4 | 5 | from typing import Any
|
5 |
| -from urllib.parse import urlparse |
| 6 | +from urllib.parse import urlencode, urlparse |
6 | 7 |
|
7 | 8 | from buildbot.changes.base import ChangeSource
|
8 | 9 | from buildbot.config.builder import BuilderConfig
|
9 | 10 | from buildbot.plugins import util
|
10 | 11 | from buildbot.reporters.base import ReporterBase
|
11 | 12 | from buildbot.reporters.gitlab import GitLabStatusPush
|
| 13 | +from buildbot.util import httpclientservice |
| 14 | +from buildbot.www import resource |
12 | 15 | from buildbot.www.auth import AuthBase
|
13 | 16 | from buildbot.www.avatar import AvatarBase
|
14 | 17 | from pydantic import BaseModel
|
| 18 | +from twisted.internet import defer |
15 | 19 | from twisted.logger import Logger
|
16 | 20 | from twisted.python import log
|
17 | 21 |
|
@@ -186,7 +190,7 @@ def create_auth(self) -> AuthBase:
|
186 | 190 | raise NotImplementedError
|
187 | 191 |
|
188 | 192 | def create_avatar_method(self) -> AvatarBase | None:
|
189 |
| - return None |
| 193 | + return AvatarGitlab(gitlab_base_url=self.config.instance_url) |
190 | 194 |
|
191 | 195 | @property
|
192 | 196 | def reload_builder_name(self) -> str:
|
@@ -260,6 +264,90 @@ def run_post(self) -> Any:
|
260 | 264 | return util.SUCCESS
|
261 | 265 |
|
262 | 266 |
|
| 267 | +class AvatarGitlab(AvatarBase): |
| 268 | + name = "gitlab" |
| 269 | + |
| 270 | + gitlab_base_url: str |
| 271 | + |
| 272 | + def __init__( |
| 273 | + self, |
| 274 | + gitlab_base_url: str, |
| 275 | + debug: bool = False, |
| 276 | + verify: bool = True, |
| 277 | + ) -> None: |
| 278 | + self.gitlab_base_url = gitlab_base_url |
| 279 | + self.debug = debug |
| 280 | + self.verify = verify |
| 281 | + |
| 282 | + self.master = None |
| 283 | + self.client = None |
| 284 | + |
| 285 | + @defer.inlineCallbacks |
| 286 | + def _get_http_client( |
| 287 | + self, |
| 288 | + ) -> Generator[defer.Deferred, None, httpclientservice.HTTPSession]: |
| 289 | + if self.client is not None: |
| 290 | + return self.client |
| 291 | + |
| 292 | + headers = { |
| 293 | + "User-Agent": "Buildbot", |
| 294 | + } |
| 295 | + |
| 296 | + self.client = yield httpclientservice.HTTPSession( |
| 297 | + self.master.httpservice, |
| 298 | + self.gitlab_base_url, |
| 299 | + headers=headers, |
| 300 | + debug=self.debug, |
| 301 | + verify=self.verify, |
| 302 | + ) |
| 303 | + |
| 304 | + return self.client |
| 305 | + |
| 306 | + @defer.inlineCallbacks |
| 307 | + def getUserAvatar( # noqa: N802 |
| 308 | + self, |
| 309 | + email: str, |
| 310 | + username: str, |
| 311 | + size: str | int, |
| 312 | + defaultAvatarUrl: str, # noqa: N803 |
| 313 | + ) -> Generator[defer.Deferred, None, None]: |
| 314 | + # avatar_url = self._get_avatar_by_username(username) |
| 315 | + if isinstance(size, int): |
| 316 | + size = str(size) |
| 317 | + avatar_url = yield self._get_avatar_by_email(email, size) |
| 318 | + if not avatar_url: |
| 319 | + avatar_url = defaultAvatarUrl |
| 320 | + raise resource.Redirect(avatar_url) |
| 321 | + |
| 322 | + def _get_avatar_by_username(self, username: str) -> str | None: |
| 323 | + qs = urlencode(dict(username=username)) |
| 324 | + users = paginated_github_request( |
| 325 | + f"{self.config.instance_url}/api/v4/users?{qs}" |
| 326 | + ) |
| 327 | + if len(users) == 1: |
| 328 | + return users[0]["avatar_url"] |
| 329 | + if len(users) > 1: |
| 330 | + # TODO: log warning |
| 331 | + ... |
| 332 | + return None |
| 333 | + |
| 334 | + @defer.inlineCallbacks |
| 335 | + def _get_avatar_by_email( |
| 336 | + self, email: str, size: str | None |
| 337 | + ) -> Generator[defer.Deferred, None, str | None]: |
| 338 | + http = yield self._get_http_client() |
| 339 | + q = dict(email=email) |
| 340 | + if size is not None: |
| 341 | + q["size"] = size |
| 342 | + qs = urlencode(q) |
| 343 | + res = yield http.get(f"/api/v4/avatar?{qs}") |
| 344 | + data = yield res.json() |
| 345 | + if "avatar_url" in data: |
| 346 | + return data["avatar_url"] |
| 347 | + else: |
| 348 | + return None |
| 349 | + |
| 350 | + |
263 | 351 | def refresh_projects(config: GitlabConfig, cache_file: Path) -> list[RepoData]:
|
264 | 352 | # access level 40 == Maintainer. See https://docs.gitlab.com/api/members/#roles
|
265 | 353 | return [
|
|
0 commit comments