Skip to content

Commit 466e79b

Browse files
committed
gitlab: add avatar method
1 parent 218c466 commit 466e79b

File tree

1 file changed

+99
-2
lines changed

1 file changed

+99
-2
lines changed

buildbot_nix/buildbot_nix/gitlab_project.py

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import os
22
import signal
3+
from collections.abc import Generator
34
from pathlib import Path
45
from typing import Any
5-
from urllib.parse import urlparse
6+
from urllib.parse import urlencode, urlparse
67

78
from buildbot.changes.base import ChangeSource
89
from buildbot.config.builder import BuilderConfig
910
from buildbot.plugins import util
1011
from buildbot.reporters.base import ReporterBase
1112
from buildbot.reporters.gitlab import GitLabStatusPush
13+
from buildbot.util import httpclientservice
14+
from buildbot.www import resource
1215
from buildbot.www.auth import AuthBase
1316
from buildbot.www.avatar import AvatarBase
1417
from pydantic import BaseModel
18+
from twisted.internet import defer
1519
from twisted.logger import Logger
1620
from twisted.python import log
1721

@@ -186,7 +190,7 @@ def create_auth(self) -> AuthBase:
186190
raise NotImplementedError
187191

188192
def create_avatar_method(self) -> AvatarBase | None:
189-
return None
193+
return AvatarGitlab(config=self.config)
190194

191195
@property
192196
def reload_builder_name(self) -> str:
@@ -260,6 +264,99 @@ def run_post(self) -> Any:
260264
return util.SUCCESS
261265

262266

267+
class AvatarGitlab(AvatarBase):
268+
name = "gitlab"
269+
270+
config: GitlabConfig
271+
272+
def __init__(
273+
self,
274+
config: GitlabConfig,
275+
debug: bool = False,
276+
verify: bool = True,
277+
) -> None:
278+
self.config = config
279+
self.debug = debug
280+
self.verify = verify
281+
282+
self.master = None
283+
self.client: httpclientservice.HTTPSession | None = None
284+
285+
def _get_http_client(
286+
self,
287+
) -> httpclientservice.HTTPSession:
288+
if self.client is not None:
289+
return self.client
290+
291+
headers = {
292+
"User-Agent": "Buildbot",
293+
"Authorization": f"Bearer {self.config.token}",
294+
}
295+
296+
self.client = httpclientservice.HTTPSession(
297+
self.master.httpservice, # type: ignore[attr-defined]
298+
self.config.instance_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 | None,
311+
size: str | int,
312+
defaultAvatarUrl: str, # noqa: N803
313+
) -> Generator[defer.Deferred, str | None, None]:
314+
if isinstance(size, int):
315+
size = str(size)
316+
avatar_url = None
317+
if username is not None:
318+
avatar_url = yield self._get_avatar_by_username(username)
319+
if avatar_url is None:
320+
avatar_url = yield self._get_avatar_by_email(email, size)
321+
if not avatar_url:
322+
avatar_url = defaultAvatarUrl
323+
raise resource.Redirect(avatar_url)
324+
325+
@defer.inlineCallbacks
326+
def _get_avatar_by_username(
327+
self, username: str
328+
) -> Generator[defer.Deferred, Any, str | None]:
329+
qs = urlencode(dict(username=username))
330+
http = self._get_http_client()
331+
users = yield http.get(f"/api/v4/users?{qs}")
332+
users = yield users.json()
333+
if len(users) == 1:
334+
return users[0]["avatar_url"]
335+
if len(users) > 1:
336+
# TODO: log warning
337+
...
338+
return None
339+
340+
@defer.inlineCallbacks
341+
def _get_avatar_by_email(
342+
self, email: str, size: str | None
343+
) -> Generator[defer.Deferred, Any, str | None]:
344+
http = self._get_http_client()
345+
q = dict(email=email)
346+
if size is not None:
347+
q["size"] = size
348+
qs = urlencode(q)
349+
res = yield http.get(f"/api/v4/avatar?{qs}")
350+
data = yield res.json()
351+
# N.B: Gitlab's public avatar endpoint returns a gravatar url if there isn't an
352+
# account with a matching public email - so it should always return *something*.
353+
# See: https://docs.gitlab.com/api/avatar/#get-details-on-an-account-avatar
354+
if "avatar_url" in data:
355+
return data["avatar_url"]
356+
else:
357+
return None
358+
359+
263360
def refresh_projects(config: GitlabConfig, cache_file: Path) -> list[RepoData]:
264361
# access level 40 == Maintainer. See https://docs.gitlab.com/api/members/#roles
265362
return [

0 commit comments

Comments
 (0)