diff --git a/docs/cli-examples.rst b/docs/cli-examples.rst index 49956a60f..94ce6c88a 100644 --- a/docs/cli-examples.rst +++ b/docs/cli-examples.rst @@ -111,6 +111,12 @@ Get a specific user by id: $ gitlab user get --id 3 +Create a user impersonation token (admin-only): + +.. code-block:: console + + gitlab user-impersonation-token create --user-id 2 --name test-token --scopes api,read_user + Deploy tokens ------------- @@ -119,7 +125,7 @@ Create a deploy token for a project: .. code-block:: console $ gitlab -v project-deploy-token create --project-id 2 \ - --name bar --username root --expires-at "2021-09-09" --scopes "read_repository" + --name bar --username root --expires-at "2021-09-09" --scopes "api,read_repository" List deploy tokens for a group: @@ -127,6 +133,47 @@ List deploy tokens for a group: $ gitlab -v group-deploy-token list --group-id 3 +Resource access tokens +---------------------- + +Create a project access token: + +.. code-block:: console + + $ gitlab -v project-access-token create --project-id 2 \ + --name project-token --expires-at "2023-01-01" --scopes "api,read_repository" + +List project access tokens: + +.. code-block:: console + + $ gitlab -v project-access-token list --project-id 3 + +Revoke a project access token: + +.. code-block:: console + + $ gitlab project-access-token delete --project-id 3 --id 1 + +Create a group access token: + +.. code-block:: console + + $ gitlab -v group-access-token create --group-id 2 \ + --name group-token --expires-at "2022-01-01" --scopes "api,read_repository" + +List group access tokens: + +.. code-block:: console + + $ gitlab -v group-access-token list --group-id 3 + +Revoke a group access token: + +.. code-block:: console + + $ gitlab group-access-token delete --group-id 3 --id 1 + Packages -------- diff --git a/gitlab/v4/objects/broadcast_messages.py b/gitlab/v4/objects/broadcast_messages.py index 3beb4ace0..e3bda6871 100644 --- a/gitlab/v4/objects/broadcast_messages.py +++ b/gitlab/v4/objects/broadcast_messages.py @@ -2,7 +2,7 @@ from gitlab.base import RESTManager, RESTObject from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin -from gitlab.types import RequiredOptional +from gitlab.types import ArrayAttribute, RequiredOptional __all__ = [ "BroadcastMessage", @@ -19,11 +19,20 @@ class BroadcastMessageManager(CRUDMixin, RESTManager): _obj_cls = BroadcastMessage _create_attrs = RequiredOptional( - required=("message",), optional=("starts_at", "ends_at", "color", "font") + required=("message",), + optional=("starts_at", "ends_at", "color", "font", "target_access_levels"), ) _update_attrs = RequiredOptional( - optional=("message", "starts_at", "ends_at", "color", "font") + optional=( + "message", + "starts_at", + "ends_at", + "color", + "font", + "target_access_levels", + ) ) + _types = {"target_access_levels": ArrayAttribute} def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any diff --git a/gitlab/v4/objects/deploy_tokens.py b/gitlab/v4/objects/deploy_tokens.py index 32bb5fed1..e35bf22c5 100644 --- a/gitlab/v4/objects/deploy_tokens.py +++ b/gitlab/v4/objects/deploy_tokens.py @@ -48,7 +48,8 @@ class GroupDeployTokenManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTManag "username", ), ) - _types = {"scopes": types.CommaSeparatedListAttribute} + _list_filters = ("scopes",) + _types = {"scopes": types.ArrayAttribute} def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any @@ -74,7 +75,8 @@ class ProjectDeployTokenManager(RetrieveMixin, CreateMixin, DeleteMixin, RESTMan "username", ), ) - _types = {"scopes": types.CommaSeparatedListAttribute} + _list_filters = ("scopes",) + _types = {"scopes": types.ArrayAttribute} def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any diff --git a/gitlab/v4/objects/environments.py b/gitlab/v4/objects/environments.py index a8bd9d5dd..1961f8ae1 100644 --- a/gitlab/v4/objects/environments.py +++ b/gitlab/v4/objects/environments.py @@ -13,7 +13,7 @@ SaveMixin, UpdateMixin, ) -from gitlab.types import RequiredOptional +from gitlab.types import ArrayAttribute, RequiredOptional __all__ = [ "ProjectEnvironment", @@ -77,6 +77,7 @@ class ProjectProtectedEnvironmentManager( ), optional=("required_approval_count", "approval_rules"), ) + _types = {"deploy_access_levels": ArrayAttribute, "approval_rules": ArrayAttribute} def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any diff --git a/gitlab/v4/objects/group_access_tokens.py b/gitlab/v4/objects/group_access_tokens.py index ca3cbcfe7..5210981aa 100644 --- a/gitlab/v4/objects/group_access_tokens.py +++ b/gitlab/v4/objects/group_access_tokens.py @@ -1,5 +1,6 @@ from gitlab.base import RESTManager, RESTObject from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin +from gitlab.types import ArrayAttribute, RequiredOptional __all__ = [ "GroupAccessToken", @@ -15,3 +16,7 @@ class GroupAccessTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): _path = "/groups/{group_id}/access_tokens" _obj_cls = GroupAccessToken _from_parent_attrs = {"group_id": "id"} + _create_attrs = RequiredOptional( + required=("name", "scopes"), optional=("access_level", "expires_at") + ) + _types = {"scopes": ArrayAttribute} diff --git a/gitlab/v4/objects/invitations.py b/gitlab/v4/objects/invitations.py index 22c72f15f..43fbb2d27 100644 --- a/gitlab/v4/objects/invitations.py +++ b/gitlab/v4/objects/invitations.py @@ -3,7 +3,7 @@ from gitlab.base import RESTManager, RESTObject from gitlab.exceptions import GitlabInvitationError from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin -from gitlab.types import CommaSeparatedListAttribute, RequiredOptional +from gitlab.types import ArrayAttribute, CommaSeparatedListAttribute, RequiredOptional __all__ = [ "ProjectInvitation", @@ -48,6 +48,7 @@ class ProjectInvitationManager(InvitationMixin, RESTManager): _types = { "email": CommaSeparatedListAttribute, "user_id": CommaSeparatedListAttribute, + "tasks_to_be_done": ArrayAttribute, } def get( @@ -81,6 +82,7 @@ class GroupInvitationManager(InvitationMixin, RESTManager): _types = { "email": CommaSeparatedListAttribute, "user_id": CommaSeparatedListAttribute, + "tasks_to_be_done": ArrayAttribute, } def get( diff --git a/gitlab/v4/objects/jobs.py b/gitlab/v4/objects/jobs.py index cfe1e62ca..952c2958c 100644 --- a/gitlab/v4/objects/jobs.py +++ b/gitlab/v4/objects/jobs.py @@ -7,6 +7,7 @@ from gitlab import utils from gitlab.base import RESTManager, RESTObject from gitlab.mixins import RefreshMixin, RetrieveMixin +from gitlab.types import ArrayAttribute __all__ = [ "ProjectJob", @@ -245,6 +246,7 @@ class ProjectJobManager(RetrieveMixin, RESTManager): _obj_cls = ProjectJob _from_parent_attrs = {"project_id": "id"} _list_filters = ("scope",) + _types = {"scope": ArrayAttribute} def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> ProjectJob: return cast(ProjectJob, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/gitlab/v4/objects/members.py b/gitlab/v4/objects/members.py index af25085ec..8751fd58b 100644 --- a/gitlab/v4/objects/members.py +++ b/gitlab/v4/objects/members.py @@ -37,12 +37,16 @@ class GroupMemberManager(CRUDMixin, RESTManager): _obj_cls = GroupMember _from_parent_attrs = {"group_id": "id"} _create_attrs = RequiredOptional( - required=("access_level", "user_id"), optional=("expires_at",) + required=("access_level", "user_id"), + optional=("expires_at", "tasks_to_be_done"), ) _update_attrs = RequiredOptional( required=("access_level",), optional=("expires_at",) ) - _types = {"user_ids": types.ArrayAttribute} + _types = { + "user_ids": types.ArrayAttribute, + "tasks_to_be_done": types.ArrayAttribute, + } def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any @@ -97,12 +101,16 @@ class ProjectMemberManager(CRUDMixin, RESTManager): _obj_cls = ProjectMember _from_parent_attrs = {"project_id": "id"} _create_attrs = RequiredOptional( - required=("access_level", "user_id"), optional=("expires_at",) + required=("access_level", "user_id"), + optional=("expires_at", "tasks_to_be_done"), ) _update_attrs = RequiredOptional( required=("access_level",), optional=("expires_at",) ) - _types = {"user_ids": types.ArrayAttribute} + _types = { + "user_ids": types.ArrayAttribute, + "tasks_to_be_dones": types.ArrayAttribute, + } def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any diff --git a/gitlab/v4/objects/personal_access_tokens.py b/gitlab/v4/objects/personal_access_tokens.py index 5e4e54bd5..fa80e9a92 100644 --- a/gitlab/v4/objects/personal_access_tokens.py +++ b/gitlab/v4/objects/personal_access_tokens.py @@ -1,6 +1,6 @@ from gitlab.base import RESTManager, RESTObject from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin -from gitlab.types import RequiredOptional +from gitlab.types import ArrayAttribute, RequiredOptional __all__ = [ "PersonalAccessToken", @@ -31,3 +31,4 @@ class UserPersonalAccessTokenManager(CreateMixin, RESTManager): _create_attrs = RequiredOptional( required=("name", "scopes"), optional=("expires_at",) ) + _types = {"scopes": ArrayAttribute} diff --git a/gitlab/v4/objects/project_access_tokens.py b/gitlab/v4/objects/project_access_tokens.py index 6293f2125..185cb6f7b 100644 --- a/gitlab/v4/objects/project_access_tokens.py +++ b/gitlab/v4/objects/project_access_tokens.py @@ -1,5 +1,6 @@ from gitlab.base import RESTManager, RESTObject from gitlab.mixins import CreateMixin, DeleteMixin, ListMixin, ObjectDeleteMixin +from gitlab.types import ArrayAttribute, RequiredOptional __all__ = [ "ProjectAccessToken", @@ -15,3 +16,7 @@ class ProjectAccessTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager _path = "/projects/{project_id}/access_tokens" _obj_cls = ProjectAccessToken _from_parent_attrs = {"project_id": "id"} + _create_attrs = RequiredOptional( + required=("name", "scopes"), optional=("access_level", "expires_at") + ) + _types = {"scopes": ArrayAttribute} diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index 803239f1f..8f3b38135 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -692,6 +692,7 @@ class ProjectManager(CRUDMixin, RESTManager): "snippets_enabled", "squash_option", "tag_list", + "topics", "template_name", "template_project_id", "use_custom_template", @@ -763,6 +764,7 @@ class ProjectManager(CRUDMixin, RESTManager): "squash_option", "suggestion_commit_message", "tag_list", + "topics", "visibility", "wiki_access_level", "wiki_enabled", @@ -799,6 +801,7 @@ class ProjectManager(CRUDMixin, RESTManager): _types = { "avatar": types.ImageAttribute, "topic": types.CommaSeparatedListAttribute, + "topics": types.ArrayAttribute, } def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> Project: diff --git a/gitlab/v4/objects/releases.py b/gitlab/v4/objects/releases.py index 788c05091..7df9a1126 100644 --- a/gitlab/v4/objects/releases.py +++ b/gitlab/v4/objects/releases.py @@ -2,7 +2,7 @@ from gitlab.base import RESTManager, RESTObject from gitlab.mixins import CRUDMixin, ObjectDeleteMixin, SaveMixin -from gitlab.types import RequiredOptional +from gitlab.types import ArrayAttribute, RequiredOptional __all__ = [ "ProjectRelease", @@ -28,6 +28,7 @@ class ProjectReleaseManager(CRUDMixin, RESTManager): _update_attrs = RequiredOptional( optional=("name", "description", "milestones", "released_at") ) + _types = {"milestones": ArrayAttribute} def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any diff --git a/gitlab/v4/objects/statistics.py b/gitlab/v4/objects/statistics.py index 3176674f4..1de963e0c 100644 --- a/gitlab/v4/objects/statistics.py +++ b/gitlab/v4/objects/statistics.py @@ -2,6 +2,7 @@ from gitlab.base import RESTManager, RESTObject from gitlab.mixins import GetWithoutIdMixin, RefreshMixin +from gitlab.types import ArrayAttribute __all__ = [ "GroupIssuesStatistics", @@ -35,6 +36,8 @@ class IssuesStatistics(RefreshMixin, RESTObject): class IssuesStatisticsManager(GetWithoutIdMixin, RESTManager): _path = "/issues_statistics" _obj_cls = IssuesStatistics + _list_filters = ("iids",) + _types = {"iids": ArrayAttribute} def get(self, **kwargs: Any) -> IssuesStatistics: return cast(IssuesStatistics, super().get(**kwargs)) @@ -48,6 +51,8 @@ class GroupIssuesStatisticsManager(GetWithoutIdMixin, RESTManager): _path = "/groups/{group_id}/issues_statistics" _obj_cls = GroupIssuesStatistics _from_parent_attrs = {"group_id": "id"} + _list_filters = ("iids",) + _types = {"iids": ArrayAttribute} def get(self, **kwargs: Any) -> GroupIssuesStatistics: return cast(GroupIssuesStatistics, super().get(**kwargs)) @@ -61,6 +66,8 @@ class ProjectIssuesStatisticsManager(GetWithoutIdMixin, RESTManager): _path = "/projects/{project_id}/issues_statistics" _obj_cls = ProjectIssuesStatistics _from_parent_attrs = {"project_id": "id"} + _list_filters = ("iids",) + _types = {"iids": ArrayAttribute} def get(self, **kwargs: Any) -> ProjectIssuesStatistics: return cast(ProjectIssuesStatistics, super().get(**kwargs)) diff --git a/gitlab/v4/objects/users.py b/gitlab/v4/objects/users.py index 69d875ed9..7395313c8 100644 --- a/gitlab/v4/objects/users.py +++ b/gitlab/v4/objects/users.py @@ -23,7 +23,7 @@ SaveMixin, UpdateMixin, ) -from gitlab.types import RequiredOptional +from gitlab.types import ArrayAttribute, RequiredOptional from .custom_attributes import UserCustomAttributeManager # noqa: F401 from .events import UserEventManager # noqa: F401 @@ -543,6 +543,7 @@ class UserImpersonationTokenManager(NoUpdateMixin, RESTManager): required=("name", "scopes"), optional=("expires_at",) ) _list_filters = ("state",) + _types = {"scopes": ArrayAttribute} def get( self, id: Union[str, int], lazy: bool = False, **kwargs: Any diff --git a/tests/functional/cli/test_cli_resource_access_tokens.py b/tests/functional/cli/test_cli_resource_access_tokens.py index fe1a5e590..85136b3de 100644 --- a/tests/functional/cli/test_cli_resource_access_tokens.py +++ b/tests/functional/cli/test_cli_resource_access_tokens.py @@ -8,9 +8,41 @@ def test_list_project_access_tokens(gitlab_cli, project): assert ret.success +def test_create_project_access_token_with_scopes(gitlab_cli, project): + cmd = [ + "project-access-token", + "create", + "--project-id", + project.id, + "--name", + "test-token", + "--scopes", + "api,read_repository", + ] + ret = gitlab_cli(cmd) + + assert ret.success + + @pytest.mark.skip(reason="Requires GitLab 14.7") def test_list_group_access_tokens(gitlab_cli, group): cmd = ["group-access-token", "list", "--group-id", group.id] ret = gitlab_cli(cmd) assert ret.success + + +def test_create_group_access_token_with_scopes(gitlab_cli, group): + cmd = [ + "group-access-token", + "create", + "--group-id", + group.id, + "--name", + "test-token", + "--scopes", + "api,read_repository", + ] + ret = gitlab_cli(cmd) + + assert ret.success diff --git a/tests/functional/cli/test_cli_users.py b/tests/functional/cli/test_cli_users.py new file mode 100644 index 000000000..740201336 --- /dev/null +++ b/tests/functional/cli/test_cli_users.py @@ -0,0 +1,14 @@ +def test_create_user_impersonation_token_with_scopes(gitlab_cli, user): + cmd = [ + "user-impersonation-token", + "create", + "--user-id", + user.id, + "--name", + "test-token", + "--scopes", + "api,read_user", + ] + ret = gitlab_cli(cmd) + + assert ret.success