Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit d0a0348

Browse filesBrowse files
benjambnejch
authored andcommitted
feat: implement secure files API
1 parent fcd72fe commit d0a0348
Copy full SHA for d0a0348

File tree

Expand file treeCollapse file tree

6 files changed

+221
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

6 files changed

+221
-0
lines changed
Open diff view settings
Collapse file

‎docs/api-objects.rst‎

Copy file name to clipboardExpand all lines: docs/api-objects.rst
+1Lines changed: 1 addition & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ API examples
5252
gl_objects/repositories
5353
gl_objects/repository_tags
5454
gl_objects/search
55+
gl_objects/secure_files
5556
gl_objects/settings
5657
gl_objects/snippets
5758
gl_objects/statistics
Collapse file

‎docs/gl_objects/secure_files.rst‎

Copy file name to clipboard
+47Lines changed: 47 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
############
2+
Secure Files
3+
############
4+
5+
secure files
6+
============
7+
8+
References
9+
----------
10+
11+
* v4 API:
12+
13+
+ :class:`gitlab.v4.objects.SecureFile`
14+
+ :class:`gitlab.v4.objects.SecureFileManager`
15+
+ :attr:`gitlab.v4.objects.Project.secure_files`
16+
17+
* GitLab API: https://docs.gitlab.com/ee/api/secure_files.html
18+
19+
Examples
20+
--------
21+
22+
Get a project secure file::
23+
24+
secure_files = gl.projects.get(1, lazy=True).secure_files.get(1)
25+
print(secure_files.name)
26+
27+
List project secure files::
28+
29+
secure_files = gl.projects.get(1, lazy=True).secure_files.list()
30+
print(secure_files[0].name)
31+
32+
Create project secure file::
33+
34+
secure_file = gl.projects.get(1).secure_files.create({"name": "test", "file": "secure.txt"})
35+
36+
Download a project secure file::
37+
38+
content = secure_file.download()
39+
print(content)
40+
with open("/tmp/secure.txt", "wb") as f:
41+
secure_file.download(streamed=True, action=f.write)
42+
43+
Remove a project secure file::
44+
45+
gl.projects.get(1).secure_files.delete(1)
46+
# or
47+
secure_file.delete()
Collapse file

‎gitlab/v4/objects/__init__.py‎

Copy file name to clipboardExpand all lines: gitlab/v4/objects/__init__.py
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
from .releases import *
5454
from .repositories import *
5555
from .runners import *
56+
from .secure_files import *
5657
from .settings import *
5758
from .sidekiq import *
5859
from .snippets import *
Collapse file

‎gitlab/v4/objects/projects.py‎

Copy file name to clipboardExpand all lines: gitlab/v4/objects/projects.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
from .releases import ProjectReleaseManager # noqa: F401
8383
from .repositories import RepositoryMixin
8484
from .runners import ProjectRunnerManager # noqa: F401
85+
from .secure_files import SecureFileManager # noqa: F401
8586
from .snippets import ProjectSnippetManager # noqa: F401
8687
from .statistics import ( # noqa: F401
8788
ProjectAdditionalStatisticsManager,
@@ -209,6 +210,7 @@ class Project(RefreshMixin, SaveMixin, ObjectDeleteMixin, RepositoryMixin, RESTO
209210
remote_mirrors: "ProjectRemoteMirrorManager"
210211
repositories: ProjectRegistryRepositoryManager
211212
runners: ProjectRunnerManager
213+
secure_files: SecureFileManager
212214
services: ProjectServiceManager
213215
snippets: ProjectSnippetManager
214216
storage: "ProjectStorageManager"
Collapse file

‎gitlab/v4/objects/secure_files.py‎

Copy file name to clipboard
+69Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""
2+
GitLab API:
3+
https://docs.gitlab.com/ee/api/secure_files.html
4+
"""
5+
from typing import Any, Callable, cast, Iterator, Optional, TYPE_CHECKING, Union
6+
7+
import requests
8+
9+
from gitlab import cli
10+
from gitlab import exceptions as exc
11+
from gitlab import utils
12+
from gitlab.base import RESTManager, RESTObject
13+
from gitlab.mixins import NoUpdateMixin, ObjectDeleteMixin
14+
from gitlab.types import FileAttribute, RequiredOptional
15+
16+
__all__ = ["SecureFile", "SecureFileManager"]
17+
18+
19+
class SecureFile(ObjectDeleteMixin, RESTObject):
20+
@cli.register_custom_action("SecureFile")
21+
@exc.on_http_error(exc.GitlabGetError)
22+
def download(
23+
self,
24+
streamed: bool = False,
25+
action: Optional[Callable[[bytes], None]] = None,
26+
chunk_size: int = 1024,
27+
*,
28+
iterator: bool = False,
29+
**kwargs: Any,
30+
) -> Optional[Union[bytes, Iterator[Any]]]:
31+
"""Download the secure file.
32+
33+
Args:
34+
streamed: If True the data will be processed by chunks of
35+
`chunk_size` and each chunk is passed to `action` for
36+
treatment
37+
iterator: If True directly return the underlying response
38+
iterator
39+
action: Callable responsible of dealing with chunk of
40+
data
41+
chunk_size: Size of each chunk
42+
**kwargs: Extra options to send to the server (e.g. sudo)
43+
44+
Raises:
45+
GitlabAuthenticationError: If authentication is not correct
46+
GitlabGetError: If the artifacts could not be retrieved
47+
48+
Returns:
49+
The artifacts if `streamed` is False, None otherwise."""
50+
path = f"{self.manager.path}/{self.id}/download"
51+
result = self.manager.gitlab.http_get(
52+
path, streamed=streamed, raw=True, **kwargs
53+
)
54+
if TYPE_CHECKING:
55+
assert isinstance(result, requests.Response)
56+
return utils.response_content(
57+
result, streamed, action, chunk_size, iterator=iterator
58+
)
59+
60+
61+
class SecureFileManager(NoUpdateMixin, RESTManager):
62+
_path = "/projects/{project_id}/secure_files"
63+
_obj_cls = SecureFile
64+
_from_parent_attrs = {"project_id": "id"}
65+
_create_attrs = RequiredOptional(required=("name", "file"))
66+
_types = {"file": FileAttribute}
67+
68+
def get(self, id: Union[str, int], lazy: bool = False, **kwargs: Any) -> SecureFile:
69+
return cast(SecureFile, super().get(id=id, lazy=lazy, **kwargs))
Collapse file
+101Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""
2+
GitLab API: https://docs.gitlab.com/ee/api/secure_files.html
3+
"""
4+
5+
import pytest
6+
import responses
7+
8+
from gitlab.v4.objects import SecureFile
9+
10+
secure_file_content = {
11+
"id": 1,
12+
"name": "myfile.jks",
13+
"checksum": "16630b189ab34b2e3504f4758e1054d2e478deda510b2b08cc0ef38d12e80aac",
14+
"checksum_algorithm": "sha256",
15+
"created_at": "2022-02-22T22:22:22.222Z",
16+
"expires_at": None,
17+
"metadata": None,
18+
}
19+
20+
21+
@pytest.fixture
22+
def resp_list_secure_files():
23+
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
24+
rsps.add(
25+
method=responses.GET,
26+
url="http://localhost/api/v4/projects/1/secure_files",
27+
json=[secure_file_content],
28+
content_type="application/json",
29+
status=200,
30+
)
31+
yield rsps
32+
33+
34+
@pytest.fixture
35+
def resp_create_secure_file():
36+
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
37+
rsps.add(
38+
method=responses.POST,
39+
url="http://localhost/api/v4/projects/1/secure_files",
40+
json=secure_file_content,
41+
content_type="application/json",
42+
status=200,
43+
)
44+
yield rsps
45+
46+
47+
@pytest.fixture
48+
def resp_download_secure_file(binary_content):
49+
with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:
50+
rsps.add(
51+
method=responses.GET,
52+
url="http://localhost/api/v4/projects/1/secure_files/1",
53+
json=secure_file_content,
54+
content_type="application/json",
55+
status=200,
56+
)
57+
rsps.add(
58+
method=responses.GET,
59+
url="http://localhost/api/v4/projects/1/secure_files/1/download",
60+
body=binary_content,
61+
content_type="application/octet-stream",
62+
status=200,
63+
)
64+
yield rsps
65+
66+
67+
@pytest.fixture
68+
def resp_remove_secure_file(no_content):
69+
with responses.RequestsMock() as rsps:
70+
rsps.add(
71+
method=responses.DELETE,
72+
url="http://localhost/api/v4/projects/1/secure_files/1",
73+
json=no_content,
74+
content_type="application/json",
75+
status=204,
76+
)
77+
yield rsps
78+
79+
80+
def test_list_secure_files(project, resp_list_secure_files):
81+
secure_files = project.secure_files.list()
82+
assert len(secure_files) == 1
83+
assert secure_files[0].id == 1
84+
assert secure_files[0].name == "myfile.jks"
85+
86+
87+
def test_create_secure_file(project, resp_create_secure_file):
88+
secure_files = project.secure_files.create({"name": "test", "file": "myfile.jks"})
89+
assert secure_files.id == 1
90+
assert secure_files.name == "myfile.jks"
91+
92+
93+
def test_download_secure_file(project, binary_content, resp_download_secure_file):
94+
secure_file = project.secure_files.get(1)
95+
secure_content = secure_file.download()
96+
assert isinstance(secure_file, SecureFile)
97+
assert secure_content == binary_content
98+
99+
100+
def test_remove_secure_file(project, resp_remove_secure_file):
101+
project.secure_files.delete(1)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.