diff options
author | Ben Brown <ben@demerara.io> | 2022-11-07 22:22:26 +0000 |
---|---|---|
committer | Nejc Habjan <hab.nejc@gmail.com> | 2022-11-10 13:16:57 +0100 |
commit | d0a034878fabfd8409134aa8b7ffeeb40219683c (patch) | |
tree | d3291921754730426ee36a659596fa151d665d25 | |
parent | fcd72fe243daa0623abfde267c7ab1c6866bcd52 (diff) | |
download | gitlab-d0a034878fabfd8409134aa8b7ffeeb40219683c.tar.gz |
feat: implement secure files API
-rw-r--r-- | docs/api-objects.rst | 1 | ||||
-rw-r--r-- | docs/gl_objects/secure_files.rst | 47 | ||||
-rw-r--r-- | gitlab/v4/objects/__init__.py | 1 | ||||
-rw-r--r-- | gitlab/v4/objects/projects.py | 2 | ||||
-rw-r--r-- | gitlab/v4/objects/secure_files.py | 69 | ||||
-rw-r--r-- | tests/unit/objects/test_secure_files.py | 101 |
6 files changed, 221 insertions, 0 deletions
diff --git a/docs/api-objects.rst b/docs/api-objects.rst index c025056..cccf1c6 100644 --- a/docs/api-objects.rst +++ b/docs/api-objects.rst @@ -52,6 +52,7 @@ API examples gl_objects/repositories gl_objects/repository_tags gl_objects/search + gl_objects/secure_files gl_objects/settings gl_objects/snippets gl_objects/statistics diff --git a/docs/gl_objects/secure_files.rst b/docs/gl_objects/secure_files.rst new file mode 100644 index 0000000..e7374bb --- /dev/null +++ b/docs/gl_objects/secure_files.rst @@ -0,0 +1,47 @@ +############ +Secure Files +############ + +secure files +============ + +References +---------- + +* v4 API: + + + :class:`gitlab.v4.objects.SecureFile` + + :class:`gitlab.v4.objects.SecureFileManager` + + :attr:`gitlab.v4.objects.Project.secure_files` + +* GitLab API: https://docs.gitlab.com/ee/api/secure_files.html + +Examples +-------- + +Get a project secure file:: + + secure_files = gl.projects.get(1, lazy=True).secure_files.get(1) + print(secure_files.name) + +List project secure files:: + + secure_files = gl.projects.get(1, lazy=True).secure_files.list() + print(secure_files[0].name) + +Create project secure file:: + + secure_file = gl.projects.get(1).secure_files.create({"name": "test", "file": "secure.txt"}) + +Download a project secure file:: + + content = secure_file.download() + print(content) + with open("/tmp/secure.txt", "wb") as f: + secure_file.download(streamed=True, action=f.write) + +Remove a project secure file:: + + gl.projects.get(1).secure_files.delete(1) + # or + secure_file.delete() diff --git a/gitlab/v4/objects/__init__.py b/gitlab/v4/objects/__init__.py index f1012a0..56c17d5 100644 --- a/gitlab/v4/objects/__init__.py +++ b/gitlab/v4/objects/__init__.py @@ -53,6 +53,7 @@ from .push_rules import * from .releases import * from .repositories import * from .runners import * +from .secure_files import * from .settings import * from .sidekiq import * from .snippets import * diff --git a/gitlab/v4/objects/projects.py b/gitlab/v4/objects/projects.py index daba896..fad21eb 100644 --- a/gitlab/v4/objects/projects.py +++ b/gitlab/v4/objects/projects.py @@ -82,6 +82,7 @@ from .push_rules import ProjectPushRulesManager # noqa: F401 from .releases import ProjectReleaseManager # noqa: F401 from .repositories import RepositoryMixin from .runners import ProjectRunnerManager # noqa: F401 +from .secure_files import SecureFileManager # noqa: F401 from .snippets import ProjectSnippetManager # noqa: F401 from .statistics import ( # noqa: F401 ProjectAdditionalStatisticsManager, @@ -209,6 +210,7 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO remote_mirrors: "ProjectRemoteMirrorManager" repositories: ProjectRegistryRepositoryManager runners: ProjectRunnerManager + secure_files: SecureFileManager services: ProjectServiceManager snippets: ProjectSnippetManager storage: "ProjectStorageManager" diff --git a/gitlab/v4/objects/secure_files.py b/gitlab/v4/objects/secure_files.py new file mode 100644 index 0000000..ce8d12d --- /dev/null +++ b/gitlab/v4/objects/secure_files.py @@ -0,0 +1,69 @@ +""" +GitLab API: +https://docs.gitlab.com/ee/api/secure_files.html +""" +from typing import Any, Callable, cast, Iterator, Optional, TYPE_CHECKING, Union + +import requests + +from gitlab import cli +from gitlab import exceptions as exc +from gitlab import utils +from gitlab.base import RESTManager, RESTObject +from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin +from gitlab.types import FileAttribute, RequiredOptional + +__all__ = ["SecureFile", "SecureFileManager"] + + +class SecureFile(ObjectDeleteMixin, RESTObject): + @cli.register_custom_action("SecureFile") + @exc.on_http_error(exc.GitlabGetError) + def download( + self, + streamed: bool = False, + action: Optional[Callable[[bytes], None]] = None, + chunk_size: int = 1024, + *, + iterator: bool = False, + **kwargs: Any, + ) -> Optional[Union[bytes, Iterator[Any]]]: + """Download the secure file. + + Args: + streamed: If True the data will be processed by chunks of + `chunk_size` and each chunk is passed to `action` for + treatment + iterator: If True directly return the underlying response + iterator + action: Callable responsible of dealing with chunk of + data + chunk_size: Size of each chunk + **kwargs: Extra options to send to the server (e.g. sudo) + + Raises: + GitlabAuthenticationError: If authentication is not correct + GitlabGetError: If the artifacts could not be retrieved + + Returns: + The artifacts if `streamed` is False, None otherwise.""" + path = f"{self.manager.path}/{self.id}/download" + result = self.manager.gitlab.http_get( + path, streamed=streamed, raw=True, **kwargs + ) + if TYPE_CHECKING: + assert isinstance(result, requests.Response) + return utils.response_content( + result, streamed, action, chunk_size, iterator=iterator + ) + + +class SecureFileManager(NoUpdateMixin, RESTManager): + _path = "/projects/{project_id}/secure_files" + _obj_cls = SecureFile + _from_parent_attrs = {"project_id": "id"} + _create_attrs = RequiredOptional(required=("name", "file")) + _types = {"file": FileAttribute} + + def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> SecureFile: + return cast(SecureFile, super().get(id=id, lazy=lazy, **kwargs)) diff --git a/tests/unit/objects/test_secure_files.py b/tests/unit/objects/test_secure_files.py new file mode 100644 index 0000000..b614dc2 --- /dev/null +++ b/tests/unit/objects/test_secure_files.py @@ -0,0 +1,101 @@ +""" +GitLab API: https://docs.gitlab.com/ee/api/secure_files.html +""" + +import pytest +import responses + +from gitlab.v4.objects import SecureFile + +secure_file_content = { + "id": 1, + "name": "myfile.jks", + "checksum": "16630b189ab34b2e3504f4758e1054d2e478deda510b2b08cc0ef38d12e80aac", + "checksum_algorithm": "sha256", + "created_at": "2022-02-22T22:22:22.222Z", + "expires_at": None, + "metadata": None, +} + + +@pytest.fixture +def resp_list_secure_files(): + with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: + rsps.add( + method=responses.GET, + url="http://localhost/api/v4/projects/1/secure_files", + json=[secure_file_content], + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_create_secure_file(): + with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: + rsps.add( + method=responses.POST, + url="http://localhost/api/v4/projects/1/secure_files", + json=secure_file_content, + content_type="application/json", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_download_secure_file(binary_content): + with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps: + rsps.add( + method=responses.GET, + url="http://localhost/api/v4/projects/1/secure_files/1", + json=secure_file_content, + content_type="application/json", + status=200, + ) + rsps.add( + method=responses.GET, + url="http://localhost/api/v4/projects/1/secure_files/1/download", + body=binary_content, + content_type="application/octet-stream", + status=200, + ) + yield rsps + + +@pytest.fixture +def resp_remove_secure_file(no_content): + with responses.RequestsMock() as rsps: + rsps.add( + method=responses.DELETE, + url="http://localhost/api/v4/projects/1/secure_files/1", + json=no_content, + content_type="application/json", + status=204, + ) + yield rsps + + +def test_list_secure_files(project, resp_list_secure_files): + secure_files = project.secure_files.list() + assert len(secure_files) == 1 + assert secure_files[0].id == 1 + assert secure_files[0].name == "myfile.jks" + + +def test_create_secure_file(project, resp_create_secure_file): + secure_files = project.secure_files.create({"name": "test", "file": "myfile.jks"}) + assert secure_files.id == 1 + assert secure_files.name == "myfile.jks" + + +def test_download_secure_file(project, binary_content, resp_download_secure_file): + secure_file = project.secure_files.get(1) + secure_content = secure_file.download() + assert isinstance(secure_file, SecureFile) + assert secure_content == binary_content + + +def test_remove_secure_file(project, resp_remove_secure_file): + project.secure_files.delete(1) |