diff --git a/gitlab/client.py b/gitlab/client.py index 37dd4c2e6..a313b7dbc 100644 --- a/gitlab/client.py +++ b/gitlab/client.py @@ -214,6 +214,8 @@ def __init__( """See :class:`~gitlab.v4.objects.TopicManager`""" self.statistics = objects.ApplicationStatisticsManager(self) """See :class:`~gitlab.v4.objects.ApplicationStatisticsManager`""" + self.service_accounts = objects.ServiceAccountManager(self) + """See :class:`~gitlab.v4.objects.ServiceAccountManager`""" def __enter__(self) -> Gitlab: return self diff --git a/gitlab/v4/objects/service_accounts.py b/gitlab/v4/objects/service_accounts.py index bf6f53d4f..493c91d8d 100644 --- a/gitlab/v4/objects/service_accounts.py +++ b/gitlab/v4/objects/service_accounts.py @@ -1,14 +1,55 @@ 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, +) +from gitlab.types import ArrayAttribute, RequiredOptional -__all__ = ["GroupServiceAccount", "GroupServiceAccountManager"] +__all__ = [ + "ServiceAccount", + "ServiceAccountManager", + "GroupServiceAccount", + "GroupServiceAccountManager", + "GroupServiceAccountAccessToken", + "GroupServiceAccountAccessTokenManager", +] -class GroupServiceAccount(ObjectDeleteMixin, RESTObject): +class GroupServiceAccountAccessToken(ObjectRotateMixin, RESTObject): + pass + + +class GroupServiceAccountAccessTokenManager( + CreateMixin[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 = RequiredOptional( + required=("name", "scopes"), optional=("expires_at",) + ) + _types = {"scopes": ArrayAttribute} + + +class ServiceAccount(RESTObject): pass +class ServiceAccountManager(CreateMixin[ServiceAccount], ListMixin[ServiceAccount]): + _path = "/service_accounts" + _obj_cls = ServiceAccount + _create_attrs = RequiredOptional(optional=("name", "username", "email")) + + +class GroupServiceAccount(ObjectDeleteMixin, RESTObject): + access_tokens: GroupServiceAccountAccessTokenManager + + class GroupServiceAccountManager( CreateMixin[GroupServiceAccount], DeleteMixin[GroupServiceAccount], diff --git a/tests/unit/objects/test_groups.py b/tests/unit/objects/test_groups.py index 7d1510c8d..1904cc98e 100644 --- a/tests/unit/objects/test_groups.py +++ b/tests/unit/objects/test_groups.py @@ -84,10 +84,23 @@ } service_account_content = { + "id": 42, "name": "gitlab-service-account", "username": "gitlab-service-account", } +service_account_access_token_content = { + "id": 1, + "name": "service_account_access_token", + "revoked": False, + "scopes": ["api"], + "user_id": 42, + "last_used": None, + "active": True, + "expires_at": None, + "token": "abcdefg12345", +} + @pytest.fixture def resp_groups(): @@ -343,6 +356,44 @@ def resp_create_group_service_account(): yield rsps +@pytest.fixture +def resp_delete_group_service_account(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.POST, + url="http://localhost/api/v4/groups/1/service_accounts", + json=service_account_content, + content_type="application/json", + status=200, + ) + rsps.add( + method=responses.DELETE, + url="http://localhost/api/v4/groups/1/service_accounts/42", + status=204, + ) + yield rsps + + +@pytest.fixture +def resp_create_group_service_account_access_token(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.POST, + url="http://localhost/api/v4/groups/1/service_accounts", + json=service_account_content, + content_type="application/json", + status=200, + ) + rsps.add( + method=responses.POST, + url="http://localhost/api/v4/groups/1/service_accounts/42/personal_access_tokens", + json=service_account_access_token_content, + content_type="application/json", + status=200, + ) + yield rsps + + def test_get_group(gl, resp_groups): data = gl.groups.get(1) assert isinstance(data, gitlab.v4.objects.Group) @@ -489,3 +540,24 @@ def test_create_group_service_account(group, resp_create_group_service_account): ) assert service_account.name == "gitlab-service-account" assert service_account.username == "gitlab-service-account" + + +def test_delete_group_service_account(group, resp_delete_group_service_account): + service_account = group.service_accounts.create( + {"name": "gitlab-service-account", "username": "gitlab-service-account"} + ) + service_account.delete() + + +def test_create_group_service_account_access_token( + group, resp_create_group_service_account_access_token +): + service_account = group.service_accounts.create( + {"name": "gitlab-service-account", "username": "gitlab-service-account"} + ) + access_token = service_account.access_tokens.create( + {"name": "service_account_access_token", "scopes": ["api"]} + ) + assert service_account.id == 42 + assert access_token.name == "service_account_access_token" + assert access_token.scopes == ["api"] diff --git a/tests/unit/objects/test_service_accounts.py b/tests/unit/objects/test_service_accounts.py new file mode 100644 index 000000000..664539f75 --- /dev/null +++ b/tests/unit/objects/test_service_accounts.py @@ -0,0 +1,76 @@ +""" +GitLab API: https://docs.gitlab.com/ee/api/user_service_accounts.html +""" + +import pytest +import responses + +create_service_account_defaults_content = { + "id": 57, + "username": "service_account_6018816a18e515214e0c34c2b33523fc", + "name": "Service account user", + "email": "service_account_6018816a18e515214e0c34c2b33523fc@noreply.gitlab.example.com", +} + + +create_service_account_content = { + "id": 42, + "username": "my_service_account", + "name": "My Service account user", + "email": "servicebot@example.com", +} + + +@pytest.fixture +def resp_create_service_account_defaults(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.POST, + url="http://localhost/api/v4/service_accounts", + json=create_service_account_defaults_content, + content_type="application/json", + status=200, + ) + + yield rsps + + +@pytest.fixture +def resp_create_service_account(): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.POST, + url="http://localhost/api/v4/service_accounts", + json=create_service_account_content, + content_type="application/json", + status=200, + ) + + yield rsps + + +def test_create_service_account_defaults(gl, resp_create_service_account_defaults): + service_account = gl.service_accounts.create() + assert service_account.id == 57 + assert ( + service_account.username == "service_account_6018816a18e515214e0c34c2b33523fc" + ) + assert service_account.name == "Service account user" + assert ( + service_account.email + == "service_account_6018816a18e515214e0c34c2b33523fc@noreply.gitlab.example.com" + ) + + +def test_create_service_account(gl, resp_create_service_account): + service_account = gl.service_accounts.create( + { + "name": "My Service account user", + "username": "my_service_account", + "email": "servicebot@example.com", + } + ) + assert service_account.id == 42 + assert service_account.username == "my_service_account" + assert service_account.name == "My Service account user" + assert service_account.email == "servicebot@example.com"