From 01de524ce39a67b549b3157bf4de827dd0568d6b Mon Sep 17 00:00:00 2001 From: ayoub mrini Date: Thu, 19 Mar 2020 23:30:40 +0100 Subject: [PATCH] feat(api): add support for Gitlab Deploy Token API --- docs/cli.rst | 13 +++ docs/gl_objects/deploy_tokens.rst | 137 ++++++++++++++++++++++++++++++ gitlab/__init__.py | 1 + gitlab/tests/test_gitlab.py | 34 ++++++++ gitlab/v4/objects.py | 39 +++++++++ tools/cli_test_v4.sh | 88 +++++++++++++++++-- tools/python_test_v4.py | 50 +++++++++++ 7 files changed, 354 insertions(+), 8 deletions(-) create mode 100644 docs/gl_objects/deploy_tokens.rst diff --git a/docs/cli.rst b/docs/cli.rst index b4a6c5e0a..b5c8e52c9 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -207,6 +207,19 @@ Get a specific user by id: $ gitlab user get --id 3 +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" + +List deploy tokens for a group: + +.. code-block:: console + + $ gitlab -v group-deploy-token list --group-id 3 + Get a list of snippets for this project: .. code-block:: console diff --git a/docs/gl_objects/deploy_tokens.rst b/docs/gl_objects/deploy_tokens.rst new file mode 100644 index 000000000..404bf0911 --- /dev/null +++ b/docs/gl_objects/deploy_tokens.rst @@ -0,0 +1,137 @@ +####### +Deploy tokens +####### + +Deploy tokens allow read-only access to your repository and registry images +without having a user and a password. + +Deploy tokens +============= + +This endpoint requires admin access. + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.DeployToken` + + :class:`gitlab.v4.objects.DeployTokenManager` + + :attr:`gitlab.Gitlab.deploytokens` + +* GitLab API: https://docs.gitlab.com/ce/api/deploy_tokens.html + +Examples +-------- + +Use the ``list()`` method to list all deploy tokens across the GitLab instance. + +:: + + # List deploy tokens + deploy_tokens = gl.deploytokens.list() + +Project deploy tokens +===================== + +This endpoint requires project maintainer access or higher. + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.ProjectDeployToken` + + :class:`gitlab.v4.objects.ProjectDeployTokenManager` + + :attr:`gitlab.v4.objects.Project.deploytokens` + +* GitLab API: https://docs.gitlab.com/ce/api/deploy_tokens.html#project-deploy-tokens + +Examples +-------- + +List the deploy tokens for a project:: + + deploy_tokens = project.deploytokens.list() + +Create a new deploy token to access registry images of a project: + +In addition to required parameters ``name`` and ``scopes``, this method accepts +the following parameters: + +* ``expires_at`` Expiration date of the deploy token. Does not expire if no value is provided. +* ``username`` Username for deploy token. Default is ``gitlab+deploy-token-{n}`` + + +:: + + deploy_token = project.deploytokens.create({'name': 'token1', 'scopes': ['read_registry'], 'username':'', 'expires_at':''}) + # show its id + print(deploy_token.id) + # show the token value. Make sure you save it, you won't be able to access it again. + print(deploy_token.token) + +.. warning:: + + With GitLab 12.9, even though ``username`` and ``expires_at`` are not required, they always have to be passed to the API. + You can set them to empty strings, see: https://gitlab.com/gitlab-org/gitlab/-/issues/211878. + Also, the ``username``'s value is ignored by the API and will be overriden with ``gitlab+deploy-token-{n}``, + see: https://gitlab.com/gitlab-org/gitlab/-/issues/211963 + These issues were fixed in GitLab 12.10. + +Remove a deploy token from the project:: + + deploy_token.delete() + # or + project.deploytokens.delete(deploy_token.id) + + +Group deploy tokens +=================== + +Reference +--------- + +* v4 API: + + + :class:`gitlab.v4.objects.GroupDeployToken` + + :class:`gitlab.v4.objects.GroupDeployTokenManager` + + :attr:`gitlab.v4.objects.Group.deploytokens` + +* GitLab API: https://docs.gitlab.com/ce/api/deploy_tokens.html#group-deploy-tokens + +Examples +-------- + +List the deploy tokens for a group:: + + deploy_tokens = group.deploytokens.list() + +Create a new deploy token to access all repositories of all projects in a group: + +In addition to required parameters ``name`` and ``scopes``, this method accepts +the following parameters: + +* ``expires_at`` Expiration date of the deploy token. Does not expire if no value is provided. +* ``username`` Username for deploy token. Default is ``gitlab+deploy-token-{n}`` + +:: + + deploy_token = group.deploytokens.create({'name': 'token1', 'scopes': ['read_repository'], 'username':'', 'expires_at':''}) + # show its id + print(deploy_token.id) + +.. warning:: + + With GitLab 12.9, even though ``username`` and ``expires_at`` are not required, they always have to be passed to the API. + You can set them to empty strings, see: https://gitlab.com/gitlab-org/gitlab/-/issues/211878. + Also, the ``username``'s value is ignored by the API and will be overriden with ``gitlab+deploy-token-{n}``, + see: https://gitlab.com/gitlab-org/gitlab/-/issues/211963 + These issues were fixed in GitLab 12.10. + +Remove a deploy token from the group:: + + deploy_token.delete() + # or + group.deploytokens.delete(deploy_token.id) + diff --git a/gitlab/__init__.py b/gitlab/__init__.py index a12ffb9cc..94e80f85a 100644 --- a/gitlab/__init__.py +++ b/gitlab/__init__.py @@ -119,6 +119,7 @@ def __init__( self.broadcastmessages = objects.BroadcastMessageManager(self) self.deploykeys = objects.DeployKeyManager(self) + self.deploytokens = objects.DeployTokenManager(self) self.geonodes = objects.GeoNodeManager(self) self.gitlabciymls = objects.GitlabciymlManager(self) self.gitignores = objects.GitignoreManager(self) diff --git a/gitlab/tests/test_gitlab.py b/gitlab/tests/test_gitlab.py index d104c7dea..113093aec 100644 --- a/gitlab/tests/test_gitlab.py +++ b/gitlab/tests/test_gitlab.py @@ -920,6 +920,40 @@ def resp_application_create(url, request): self.assertEqual(application.redirect_uri, "http://localhost:8080") self.assertEqual(application.scopes, ["api", "email"]) + def test_deploy_tokens(self): + @urlmatch( + scheme="http", + netloc="localhost", + path="/api/v4/projects/1/deploy_tokens", + method="post", + ) + def resp_deploy_token_create(url, request): + headers = {"content-type": "application/json"} + content = """{ + "id": 1, + "name": "test_deploy_token", + "username": "custom-user", + "expires_at": "2022-01-01T00:00:00.000Z", + "token": "jMRvtPNxrn3crTAGukpZ", + "scopes": [ "read_repository" ]}""" + content = content.encode("utf-8") + return response(200, content, headers, None, 5, request) + + with HTTMock(resp_deploy_token_create): + deploy_token = self.gl.projects.get(1, lazy=True).deploytokens.create( + { + "name": "test_deploy_token", + "expires_at": "2022-01-01T00:00:00.000Z", + "username": "custom-user", + "scopes": ["read_repository"], + } + ) + self.assertIsInstance(deploy_token, ProjectDeployToken) + self.assertEqual(deploy_token.id, 1), + self.assertEqual(deploy_token.expires_at, "2022-01-01T00:00:00.000Z"), + self.assertEqual(deploy_token.username, "custom-user") + self.assertEqual(deploy_token.scopes, ["read_repository"]) + def _default_config(self): fd, temp_path = tempfile.mkstemp() os.write(fd, valid_config) diff --git a/gitlab/v4/objects.py b/gitlab/v4/objects.py index 8852a1e81..116c7ecaa 100644 --- a/gitlab/v4/objects.py +++ b/gitlab/v4/objects.py @@ -695,6 +695,43 @@ class DeployKeyManager(ListMixin, RESTManager): _obj_cls = DeployKey +class DeployToken(ObjectDeleteMixin, RESTObject): + pass + + +class DeployTokenManager(ListMixin, RESTManager): + _path = "/deploy_tokens" + _obj_cls = DeployToken + + +class ProjectDeployToken(ObjectDeleteMixin, RESTObject): + pass + + +class ProjectDeployTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/projects/%(project_id)s/deploy_tokens" + _from_parent_attrs = {"project_id": "id"} + _obj_cls = ProjectDeployToken + _create_attrs = ( + ("name", "scopes",), + ("expires_at", "username",), + ) + + +class GroupDeployToken(ObjectDeleteMixin, RESTObject): + pass + + +class GroupDeployTokenManager(ListMixin, CreateMixin, DeleteMixin, RESTManager): + _path = "/groups/%(group_id)s/deploy_tokens" + _from_parent_attrs = {"group_id": "id"} + _obj_cls = GroupDeployToken + _create_attrs = ( + ("name", "scopes",), + ("expires_at", "username",), + ) + + class NotificationSettings(SaveMixin, RESTObject): _id_attr = None @@ -1301,6 +1338,7 @@ class Group(SaveMixin, ObjectDeleteMixin, RESTObject): ("subgroups", "GroupSubgroupManager"), ("variables", "GroupVariableManager"), ("clusters", "GroupClusterManager"), + ("deploytokens", "GroupDeployTokenManager"), ) @cli.register_custom_action("Group", ("to_project_id",)) @@ -4212,6 +4250,7 @@ class Project(SaveMixin, ObjectDeleteMixin, RESTObject): ("clusters", "ProjectClusterManager"), ("additionalstatistics", "ProjectAdditionalStatisticsManager"), ("issuesstatistics", "ProjectIssuesStatisticsManager"), + ("deploytokens", "ProjectDeployTokenManager"), ) @cli.register_custom_action("Project", ("submodule", "branch", "commit_sha")) diff --git a/tools/cli_test_v4.sh b/tools/cli_test_v4.sh index b9167057a..395289a2d 100755 --- a/tools/cli_test_v4.sh +++ b/tools/cli_test_v4.sh @@ -195,14 +195,6 @@ testcase "project upload" ' --filename '$(basename $0)' --filepath '$0' >/dev/null 2>&1 ' -testcase "project deletion" ' - GITLAB project delete --id "$PROJECT_ID" -' - -testcase "group deletion" ' - OUTPUT=$(try GITLAB group delete --id $GROUP_ID) -' - testcase "application settings get" ' GITLAB application-settings get >/dev/null 2>&1 ' @@ -222,3 +214,83 @@ testcase "values from files" ' echo $OUTPUT | grep -q "Multi line" ' +# Test deploy tokens +CREATE_PROJECT_DEPLOY_TOKEN_OUTPUT=$(GITLAB -v project-deploy-token create --project-id $PROJECT_ID \ + --name foo --username root --expires-at "2021-09-09" --scopes "read_registry") +CREATED_DEPLOY_TOKEN_ID=$(echo "$CREATE_PROJECT_DEPLOY_TOKEN_OUTPUT" | grep ^id: | cut -d" " -f2) +testcase "create project deploy token" ' + echo $CREATE_PROJECT_DEPLOY_TOKEN_OUTPUT | grep -q "name: foo" +' +testcase "create project deploy token" ' + echo $CREATE_PROJECT_DEPLOY_TOKEN_OUTPUT | grep -q "expires-at: 2021-09-09T00:00:00.000Z" +' +testcase "create project deploy token" ' + echo $CREATE_PROJECT_DEPLOY_TOKEN_OUTPUT | grep "scopes: " | grep -q "read_registry" +' +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/211963 is fixed +#testcase "create project deploy token" ' +# echo $CREATE_PROJECT_DEPLOY_TOKEN_OUTPUT | grep -q "username: root" +#' + +# Remove once https://gitlab.com/gitlab-org/gitlab/-/issues/211963 is fixed +testcase "create project deploy token" ' + echo $CREATE_PROJECT_DEPLOY_TOKEN_OUTPUT | grep -q "gitlab+deploy-token" +' + +LIST_DEPLOY_TOKEN_OUTPUT=$(GITLAB -v deploy-token list) +testcase "list all deploy tokens" ' + echo $LIST_DEPLOY_TOKEN_OUTPUT | grep -q "name: foo" +' +testcase "list all deploy tokens" ' + echo $LIST_DEPLOY_TOKEN_OUTPUT | grep -q "id: $CREATED_DEPLOY_TOKEN_ID" +' +testcase "list all deploy tokens" ' + echo $LIST_DEPLOY_TOKEN_OUTPUT | grep -q "expires-at: 2021-09-09T00:00:00.000Z" +' +testcase "list all deploy tokens" ' + echo $LIST_DEPLOY_TOKEN_OUTPUT | grep "scopes: " | grep -q "read_registry" +' + +testcase "list project deploy tokens" ' + OUTPUT=$(GITLAB -v project-deploy-token list --project-id $PROJECT_ID) + echo $OUTPUT | grep -q "id: $CREATED_DEPLOY_TOKEN_ID" +' +testcase "delete project deploy token" ' + GITLAB -v project-deploy-token delete --project-id $PROJECT_ID --id $CREATED_DEPLOY_TOKEN_ID + LIST_PROJECT_DEPLOY_TOKEN_OUTPUT=$(GITLAB -v project-deploy-token list --project-id $PROJECT_ID) + echo $LIST_PROJECT_DEPLOY_TOKEN_OUTPUT | grep -qv "id: $CREATED_DEPLOY_TOKEN_ID" +' +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/212523 is fixed +#testcase "delete project deploy token" ' +# LIST_DEPLOY_TOKEN_OUTPUT=$(GITLAB -v deploy-token list) +# echo $LIST_DEPLOY_TOKEN_OUTPUT | grep -qv "id: $CREATED_DEPLOY_TOKEN_ID" +#' + +CREATE_GROUP_DEPLOY_TOKEN_OUTPUT=$(GITLAB -v group-deploy-token create --group-id $GROUP_ID \ + --name bar --username root --expires-at "2021-09-09" --scopes "read_repository") +CREATED_DEPLOY_TOKEN_ID=$(echo "$CREATE_GROUP_DEPLOY_TOKEN_OUTPUT" | grep ^id: | cut -d" " -f2) +testcase "create group deploy token" ' + echo $CREATE_GROUP_DEPLOY_TOKEN_OUTPUT | grep -q "name: bar" +' +testcase "list group deploy tokens" ' + OUTPUT=$(GITLAB -v group-deploy-token list --group-id $GROUP_ID) + echo $OUTPUT | grep -q "id: $CREATED_DEPLOY_TOKEN_ID" +' +testcase "delete group deploy token" ' + GITLAB -v group-deploy-token delete --group-id $GROUP_ID --id $CREATED_DEPLOY_TOKEN_ID + LIST_GROUP_DEPLOY_TOKEN_OUTPUT=$(GITLAB -v group-deploy-token list --group-id $GROUP_ID) + echo $LIST_GROUP_DEPLOY_TOKEN_OUTPUT | grep -qv "id: $CREATED_DEPLOY_TOKEN_ID" +' +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/212523 is fixed +#testcase "delete group deploy token" ' +# LIST_DEPLOY_TOKEN_OUTPUT=$(GITLAB -v deploy-token list) +# echo $LIST_DEPLOY_TOKEN_OUTPUT | grep -qv "id: $CREATED_DEPLOY_TOKEN_ID" +#' + +testcase "project deletion" ' + GITLAB project delete --id "$PROJECT_ID" +' + +testcase "group deletion" ' + OUTPUT=$(try GITLAB group delete --id $GROUP_ID) +' diff --git a/tools/python_test_v4.py b/tools/python_test_v4.py index 69b0d3181..58ef08131 100644 --- a/tools/python_test_v4.py +++ b/tools/python_test_v4.py @@ -625,6 +625,56 @@ sudo_project.keys.delete(deploy_key.id) assert len(sudo_project.keys.list()) == 0 +# deploy tokens +deploy_token = admin_project.deploytokens.create( + { + "name": "foo", + "username": "bar", + "expires_at": "2022-01-01", + "scopes": ["read_registry"], + } +) +assert len(admin_project.deploytokens.list()) == 1 +assert gl.deploytokens.list() == admin_project.deploytokens.list() + +assert admin_project.deploytokens.list()[0].name == "foo" +assert admin_project.deploytokens.list()[0].expires_at == "2022-01-01T00:00:00.000Z" +assert admin_project.deploytokens.list()[0].scopes == ["read_registry"] +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/211963 is fixed +# assert admin_project.deploytokens.list()[0].username == "bar" +deploy_token.delete() +assert len(admin_project.deploytokens.list()) == 0 +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/212523 is fixed +# assert len(gl.deploytokens.list()) == 0 + + +deploy_token_group = gl.groups.create( + {"name": "deploy_token_group", "path": "deploy_token_group"} +) + +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/211878 is fixed +# deploy_token = group_deploy_token.deploytokens.create( +# { +# "name": "foo", +# "scopes": ["read_registry"], +# } +# ) + +# Remove once https://gitlab.com/gitlab-org/gitlab/-/issues/211878 is fixed +deploy_token = deploy_token_group.deploytokens.create( + {"name": "foo", "username": "", "expires_at": "", "scopes": ["read_repository"],} +) + +assert len(deploy_token_group.deploytokens.list()) == 1 +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/212523 is fixed +# assert gl.deploytokens.list() == deploy_token_group.deploytokens.list() +deploy_token.delete() +assert len(deploy_token_group.deploytokens.list()) == 0 +# Uncomment once https://gitlab.com/gitlab-org/gitlab/-/issues/212523 is fixed +# assert len(gl.deploytokens.list()) == 0 + +deploy_token_group.delete() + # labels # label1 = admin_project.labels.create({"name": "label1", "color": "#778899"}) # label1 = admin_project.labels.list()[0]