Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/api-objects.rst
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ API examples
gl_objects/resource_groups
gl_objects/search
gl_objects/secure_files
gl_objects/service_accounts
gl_objects/settings
gl_objects/snippets
gl_objects/statistics
Expand Down
153 changes: 153 additions & 0 deletions docs/gl_objects/service_accounts.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
################
Service Accounts
################

References
----------

* v4 API:

+ :class:`gitlab.v4.objects.ServiceAccount`
+ :class:`gitlab.v4.objects.ServiceAccountManager`
+ :class:`gitlab.v4.objects.GroupServiceAccount`
+ :class:`gitlab.v4.objects.GroupServiceAccountManager`
+ :class:`gitlab.v4.objects.GroupServiceAccountAccessToken`
+ :class:`gitlab.v4.objects.GroupServiceAccountAccessTokenManager`
+ :class:`gitlab.v4.objects.ProjectServiceAccount`
+ :class:`gitlab.v4.objects.ProjectServiceAccountManager`
+ :class:`gitlab.v4.objects.ProjectServiceAccountAccessToken`
+ :class:`gitlab.v4.objects.ProjectServiceAccountAccessTokenManager`

* GitLab API: https://docs.gitlab.com/api/service_accounts/

Instance service accounts
-------------------------

List instance service accounts::

accounts = gl.service_accounts.list()

Create an instance service account::

sa = gl.service_accounts.create({})
# with optional attributes
sa = gl.service_accounts.create({"name": "my-bot", "username": "my-bot", "email": "my-bot@example.com"})

Update an instance service account::

gl.service_accounts.update(sa.id, {"name": "renamed-bot"})
# or via the object
sa.name = "renamed-bot"
sa.save()

Group service accounts
----------------------

List group service accounts::

accounts = group.service_accounts.list()

Create a group service account::

sa = group.service_accounts.create({})
# with optional attributes
sa = group.service_accounts.create({"name": "ci-bot", "username": "ci-bot"})

Update a group service account::

group.service_accounts.update(sa.id, {"name": "renamed-bot"})
# or via the object
sa.name = "renamed-bot"
sa.save()

Delete a group service account::

group.service_accounts.delete(sa.id)
# or via the object
sa.delete()

Group service account personal access tokens
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

List tokens for a group service account::

tokens = sa.access_tokens.list()

Create a token for a group service account::

token = sa.access_tokens.create({
"name": "ci-token",
"scopes": ["api"],
"expires_at": "2026-01-01",
})
print(token.token)

Rotate a token::

token.rotate()
print(token.token)
# or directly using a token ID
new_token = sa.access_tokens.rotate(token.id)
print(new_token["token"])

Revoke a token::

sa.access_tokens.delete(token.id)
# or via the object
token.delete()

Project service accounts
------------------------

List project service accounts::

accounts = project.service_accounts.list()

Create a project service account::

sa = project.service_accounts.create({})
# with optional attributes
sa = project.service_accounts.create({"name": "ci-bot", "username": "ci-bot"})

Update a project service account::

project.service_accounts.update(sa.id, {"name": "renamed-bot"})
# or via the object
sa.name = "renamed-bot"
sa.save()

Delete a project service account::

project.service_accounts.delete(sa.id)
# or via the object
sa.delete()

Project service account personal access tokens
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

List tokens for a project service account::

tokens = sa.access_tokens.list()

Create a token for a project service account::

token = sa.access_tokens.create({
"name": "ci-token",
"scopes": ["read_repository"],
"expires_at": "2026-01-01",
})
print(token.token)

Rotate a token::

token.rotate()
print(token.token)
# or directly using a token ID
new_token = sa.access_tokens.rotate(token.id)
print(new_token["token"])

Revoke a token::

sa.access_tokens.delete(token.id)
# or via the object
token.delete()
2 changes: 2 additions & 0 deletions gitlab/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ def __init__(
"""See :class:`~gitlab.v4.objects.PersonalAccessTokenManager`"""
self.topics = objects.TopicManager(self)
"""See :class:`~gitlab.v4.objects.TopicManager`"""
self.service_accounts = objects.ServiceAccountManager(self)
"""See :class:`~gitlab.v4.objects.ServiceAccountManager`"""
self.statistics = objects.ApplicationStatisticsManager(self)
"""See :class:`~gitlab.v4.objects.ApplicationStatisticsManager`"""

Expand Down
2 changes: 2 additions & 0 deletions gitlab/v4/objects/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
from .resource_groups import ProjectResourceGroupManager
from .runners import ProjectRunnerManager # noqa: F401
from .secure_files import ProjectSecureFileManager # noqa: F401
from .service_accounts import ProjectServiceAccountManager # noqa: F401
from .snippets import ProjectSnippetManager # noqa: F401
from .statistics import ( # noqa: F401
ProjectAdditionalStatisticsManager,
Expand Down Expand Up @@ -247,6 +248,7 @@ class Project(
repositories: ProjectRegistryRepositoryManager
runners: ProjectRunnerManager
secure_files: ProjectSecureFileManager
service_accounts: ProjectServiceAccountManager
services: ProjectServiceManager
snippets: ProjectSnippetManager
external_status_checks: ProjectExternalStatusCheckManager
Expand Down
145 changes: 140 additions & 5 deletions gitlab/v4/objects/service_accounts.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,155 @@
"""
GitLab API: https://docs.gitlab.com/api/service_accounts/
"""

from gitlab.base import RESTObject
from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin
from gitlab.types import RequiredOptional
from gitlab.mixins import (
CreateMixin,
DeleteMixin,
ListMixin,
ObjectDeleteMixin,
ObjectRotateMixin,
RotateMixin,
SaveMixin,
UpdateMethod,
UpdateMixin,
)
from gitlab.types import ArrayAttribute, RequiredOptional

__all__ = [
"ServiceAccount",
"ServiceAccountManager",
"GroupServiceAccount",
"GroupServiceAccountManager",
"GroupServiceAccountAccessToken",
"GroupServiceAccountAccessTokenManager",
"ProjectServiceAccount",
"ProjectServiceAccountManager",
"ProjectServiceAccountAccessToken",
"ProjectServiceAccountAccessTokenManager",
]

_SA_ACCOUNT_ATTRS = RequiredOptional(optional=("name", "username", "email"))

_SA_TOKEN_CREATE_ATTRS = RequiredOptional(
required=("name", "scopes"), optional=("description", "expires_at")
)

_SA_TOKEN_LIST_FILTERS = (
"created_after",
"created_before",
"expires_after",
"expires_before",
"last_used_after",
"last_used_before",
"revoked",
"search",
"sort",
"state",
)

__all__ = ["GroupServiceAccount", "GroupServiceAccountManager"]

# ---------------------------------------------------------------------------
# Instance-level service accounts
# ---------------------------------------------------------------------------

class GroupServiceAccount(ObjectDeleteMixin, RESTObject):

class ServiceAccount(SaveMixin, RESTObject):
pass


class ServiceAccountManager(
CreateMixin[ServiceAccount], ListMixin[ServiceAccount], UpdateMixin[ServiceAccount]
):
_path = "/service_accounts"
_obj_cls = ServiceAccount
_create_attrs = _SA_ACCOUNT_ATTRS
_update_attrs = _SA_ACCOUNT_ATTRS
_update_method = UpdateMethod.PATCH
_list_filters = ("order_by", "sort")


# ---------------------------------------------------------------------------
# Group-level service accounts
# ---------------------------------------------------------------------------


class GroupServiceAccountAccessToken(ObjectDeleteMixin, ObjectRotateMixin, RESTObject):
pass


class GroupServiceAccountAccessTokenManager(
CreateMixin[GroupServiceAccountAccessToken],
DeleteMixin[GroupServiceAccountAccessToken],
ListMixin[GroupServiceAccountAccessToken],
RotateMixin[GroupServiceAccountAccessToken],
):
_path = "/groups/{group_id}/service_accounts/{user_id}/personal_access_tokens"
_obj_cls = GroupServiceAccountAccessToken
_from_parent_attrs = {"group_id": "group_id", "user_id": "id"}
_create_attrs = _SA_TOKEN_CREATE_ATTRS
_types = {"scopes": ArrayAttribute}
_list_filters = _SA_TOKEN_LIST_FILTERS


class GroupServiceAccount(SaveMixin, ObjectDeleteMixin, RESTObject):
access_tokens: GroupServiceAccountAccessTokenManager


class GroupServiceAccountManager(
CreateMixin[GroupServiceAccount],
DeleteMixin[GroupServiceAccount],
ListMixin[GroupServiceAccount],
UpdateMixin[GroupServiceAccount],
):
_path = "/groups/{group_id}/service_accounts"
_obj_cls = GroupServiceAccount
_from_parent_attrs = {"group_id": "id"}
_create_attrs = RequiredOptional(optional=("name", "username"))
_create_attrs = _SA_ACCOUNT_ATTRS
_update_attrs = _SA_ACCOUNT_ATTRS
_update_method = UpdateMethod.PATCH
_list_filters = ("order_by", "sort")


# ---------------------------------------------------------------------------
# Project-level service accounts
# ---------------------------------------------------------------------------


class ProjectServiceAccountAccessToken(
ObjectDeleteMixin, ObjectRotateMixin, RESTObject
):
pass


class ProjectServiceAccountAccessTokenManager(
CreateMixin[ProjectServiceAccountAccessToken],
DeleteMixin[ProjectServiceAccountAccessToken],
ListMixin[ProjectServiceAccountAccessToken],
RotateMixin[ProjectServiceAccountAccessToken],
):
_path = "/projects/{project_id}/service_accounts/{user_id}/personal_access_tokens"
_obj_cls = ProjectServiceAccountAccessToken
_from_parent_attrs = {"project_id": "project_id", "user_id": "id"}
_create_attrs = _SA_TOKEN_CREATE_ATTRS
_types = {"scopes": ArrayAttribute}
_list_filters = _SA_TOKEN_LIST_FILTERS


class ProjectServiceAccount(SaveMixin, ObjectDeleteMixin, RESTObject):
access_tokens: ProjectServiceAccountAccessTokenManager


class ProjectServiceAccountManager(
CreateMixin[ProjectServiceAccount],
DeleteMixin[ProjectServiceAccount],
ListMixin[ProjectServiceAccount],
UpdateMixin[ProjectServiceAccount],
):
_path = "/projects/{project_id}/service_accounts"
_obj_cls = ProjectServiceAccount
_from_parent_attrs = {"project_id": "id"}
_create_attrs = _SA_ACCOUNT_ATTRS
_update_attrs = _SA_ACCOUNT_ATTRS
_update_method = UpdateMethod.PATCH
_list_filters = ("order_by", "sort")
Loading