Skip to content

Commit 2eeb0a4

Browse files
committed
feat(objects): add a complete job artifacts manager
1 parent 4b798fc commit 2eeb0a4

File tree

3 files changed

+142
-89
lines changed

3 files changed

+142
-89
lines changed

docs/gl_objects/pipelines_and_jobs.rst

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,12 @@ List the jobs of a pipeline::
242242

243243
Get the artifacts of a job::
244244

245-
build_or_job.artifacts()
245+
job.artifacts.download()
246+
247+
.. attention::
248+
249+
An older method ``job.artifacts()`` is deprecated and will be
250+
removed in a future version.
246251

247252
Get the artifacts of a job by its name from the latest successful pipeline of
248253
a branch or tag::
@@ -264,13 +269,13 @@ You can download artifacts as a stream. Provide a callable to handle the
264269
stream::
265270

266271
with open("archive.zip", "wb") as f:
267-
build_or_job.artifacts(streamed=True, action=f.write)
272+
job.artifacts.download(streamed=True, action=f.write)
268273

269274
You can also directly stream the output into a file, and unzip it afterwards::
270275

271276
zipfn = "___artifacts.zip"
272277
with open(zipfn, "wb") as f:
273-
build_or_job.artifacts(streamed=True, action=f.write)
278+
job.artifacts.download(streamed=True, action=f.write)
274279
subprocess.run(["unzip", "-bo", zipfn])
275280
os.unlink(zipfn)
276281

@@ -293,7 +298,12 @@ Delete all artifacts of a project that can be deleted::
293298

294299
Get a single artifact file::
295300

296-
build_or_job.artifact('path/to/file')
301+
job.artifacts.raw('path/to/file')
302+
303+
.. attention::
304+
305+
An older method ``job.artifact()`` is deprecated and will be
306+
removed in a future version.
297307

298308
Get a single artifact file by branch and job::
299309

@@ -306,15 +316,15 @@ Get a single artifact file by branch and job::
306316

307317
Mark a job artifact as kept when expiration is set::
308318

309-
build_or_job.keep_artifacts()
319+
job.keep_artifacts()
310320

311321
Delete the artifacts of a job::
312322

313-
build_or_job.delete_artifacts()
323+
job.delete_artifacts()
314324

315325
Get a job trace::
316326

317-
build_or_job.trace()
327+
job.trace()
318328

319329
.. warning::
320330

@@ -323,16 +333,16 @@ Get a job trace::
323333

324334
Cancel/retry a job::
325335

326-
build_or_job.cancel()
327-
build_or_job.retry()
336+
job.cancel()
337+
job.retry()
328338

329339
Play (trigger) a job::
330340

331-
build_or_job.play()
341+
job.play()
332342

333343
Erase a job (artifacts and trace)::
334344

335-
build_or_job.erase()
345+
job.erase()
336346

337347

338348
Pipeline bridges

gitlab/v4/objects/artifacts.py

Lines changed: 115 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
GitLab API:
33
https://docs.gitlab.com/ee/api/job_artifacts.html
44
"""
5+
import warnings
56
from typing import Any, Callable, Iterator, Optional, TYPE_CHECKING, Union
67

78
import requests
@@ -11,7 +12,12 @@
1112
from gitlab import utils
1213
from gitlab.base import RESTManager, RESTObject
1314

14-
__all__ = ["ProjectArtifact", "ProjectArtifactManager"]
15+
__all__ = [
16+
"ProjectArtifact",
17+
"ProjectArtifactManager",
18+
"ProjectJobArtifact",
19+
"ProjectJobArtifactManager",
20+
]
1521

1622

1723
class ProjectArtifact(RESTObject):
@@ -165,3 +171,111 @@ def raw(
165171
return utils.response_content(
166172
result, streamed, action, chunk_size, iterator=iterator
167173
)
174+
175+
176+
class ProjectJobArtifact(RESTObject):
177+
"""Dummy object to manage custom actions on artifacts"""
178+
179+
_id_attr = "artifact_path"
180+
181+
182+
class ProjectJobArtifactManager(RESTManager):
183+
_path = "/projects/{project_id}/jobs/{job_id}/artifacts"
184+
_from_parent_attrs = {"project_id": "project_id", "job_id": "id"}
185+
186+
@cli.register_custom_action("ProjectJob", custom_action="artifacts")
187+
def __call__(
188+
self,
189+
*args: Any,
190+
**kwargs: Any,
191+
) -> Optional[Union[bytes, Iterator[Any]]]:
192+
warnings.warn(
193+
"The job.artifacts() method is deprecated and will be "
194+
"removed in a future version. Use job.artifacts.download() instead.\n",
195+
DeprecationWarning,
196+
)
197+
return self.download(
198+
*args,
199+
**kwargs,
200+
)
201+
202+
@cli.register_custom_action("ProjectJobArtifactManager")
203+
@exc.on_http_error(exc.GitlabGetError)
204+
def download(
205+
self,
206+
streamed: bool = False,
207+
action: Optional[Callable[..., Any]] = None,
208+
chunk_size: int = 1024,
209+
iterator: bool = False,
210+
**kwargs: Any,
211+
) -> Optional[Union[bytes, Iterator[Any]]]:
212+
"""Get the job artifacts.
213+
214+
Args:
215+
streamed: If True the data will be processed by chunks of
216+
`chunk_size` and each chunk is passed to `action` for
217+
treatment
218+
action: Callable responsible of dealing with chunk of
219+
data
220+
chunk_size: Size of each chunk
221+
iterator: If True directly return the underlying response
222+
iterator
223+
**kwargs: Extra options to send to the server (e.g. sudo)
224+
225+
Raises:
226+
GitlabAuthenticationError: If authentication is not correct
227+
GitlabGetError: If the artifacts could not be retrieved
228+
229+
Returns:
230+
The artifacts if `streamed` is False, None otherwise.
231+
"""
232+
if TYPE_CHECKING:
233+
assert self.path is not None
234+
result = self.gitlab.http_get(self.path, streamed=streamed, raw=True, **kwargs)
235+
236+
if TYPE_CHECKING:
237+
assert isinstance(result, requests.Response)
238+
return utils.response_content(
239+
result, streamed, action, chunk_size, iterator=iterator
240+
)
241+
242+
@cli.register_custom_action("ProjectJobArtifactManager", ("artifact_path",))
243+
@cli.register_custom_action("ProjectJob")
244+
@exc.on_http_error(exc.GitlabGetError)
245+
def raw(
246+
self,
247+
path: str,
248+
streamed: bool = False,
249+
action: Optional[Callable[..., Any]] = None,
250+
chunk_size: int = 1024,
251+
iterator: bool = False,
252+
**kwargs: Any,
253+
) -> Optional[Union[bytes, Iterator[Any]]]:
254+
"""Get a single artifact file from within the job's artifacts archive.
255+
256+
Args:
257+
path: Path of the artifact
258+
streamed: If True the data will be processed by chunks of
259+
`chunk_size` and each chunk is passed to `action` for
260+
treatment
261+
action: Callable responsible of dealing with chunk of
262+
data
263+
chunk_size: Size of each chunk
264+
iterator: If True directly return the underlying response
265+
iterator
266+
**kwargs: Extra options to send to the server (e.g. sudo)
267+
268+
Raises:
269+
GitlabAuthenticationError: If authentication is not correct
270+
GitlabGetError: If the artifacts could not be retrieved
271+
272+
Returns:
273+
The artifacts if `streamed` is False, None otherwise.
274+
"""
275+
path = f"{self.path}/{path}"
276+
result = self.gitlab.http_get(path, streamed=streamed, raw=True, **kwargs)
277+
if TYPE_CHECKING:
278+
assert isinstance(result, requests.Response)
279+
return utils.response_content(
280+
result, streamed, action, chunk_size, iterator=iterator
281+
)

gitlab/v4/objects/jobs.py

Lines changed: 6 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@
88
from gitlab.base import RESTManager, RESTObject
99
from gitlab.mixins import RefreshMixin, RetrieveMixin
1010

11+
from .artifacts import ProjectJobArtifactManager # noqa: F401
12+
1113
__all__ = [
1214
"ProjectJob",
1315
"ProjectJobManager",
1416
]
1517

1618

1719
class ProjectJob(RefreshMixin, RESTObject):
20+
artifacts: ProjectJobArtifactManager
21+
1822
@cli.register_custom_action("ProjectJob")
1923
@exc.on_http_error(exc.GitlabJobCancelError)
2024
def cancel(self, **kwargs: Any) -> Dict[str, Any]:
@@ -111,89 +115,14 @@ def delete_artifacts(self, **kwargs: Any) -> None:
111115
path = f"{self.manager.path}/{self.encoded_id}/artifacts"
112116
self.manager.gitlab.http_delete(path, **kwargs)
113117

114-
@cli.register_custom_action("ProjectJob")
115-
@exc.on_http_error(exc.GitlabGetError)
116-
def artifacts(
117-
self,
118-
streamed: bool = False,
119-
action: Optional[Callable[..., Any]] = None,
120-
chunk_size: int = 1024,
121-
*,
122-
iterator: bool = False,
123-
**kwargs: Any,
124-
) -> Optional[Union[bytes, Iterator[Any]]]:
125-
"""Get the job artifacts.
126-
127-
Args:
128-
streamed: If True the data will be processed by chunks of
129-
`chunk_size` and each chunk is passed to `action` for
130-
treatment
131-
iterator: If True directly return the underlying response
132-
iterator
133-
action: Callable responsible of dealing with chunk of
134-
data
135-
chunk_size: Size of each chunk
136-
**kwargs: Extra options to send to the server (e.g. sudo)
137-
138-
Raises:
139-
GitlabAuthenticationError: If authentication is not correct
140-
GitlabGetError: If the artifacts could not be retrieved
141-
142-
Returns:
143-
The artifacts if `streamed` is False, None otherwise.
144-
"""
145-
path = f"{self.manager.path}/{self.encoded_id}/artifacts"
146-
result = self.manager.gitlab.http_get(
147-
path, streamed=streamed, raw=True, **kwargs
148-
)
149-
if TYPE_CHECKING:
150-
assert isinstance(result, requests.Response)
151-
return utils.response_content(
152-
result, streamed, action, chunk_size, iterator=iterator
153-
)
154-
155118
@cli.register_custom_action("ProjectJob")
156119
@exc.on_http_error(exc.GitlabGetError)
157120
def artifact(
158121
self,
159-
path: str,
160-
streamed: bool = False,
161-
action: Optional[Callable[..., Any]] = None,
162-
chunk_size: int = 1024,
163-
*,
164-
iterator: bool = False,
122+
*args: Any,
165123
**kwargs: Any,
166124
) -> Optional[Union[bytes, Iterator[Any]]]:
167-
"""Get a single artifact file from within the job's artifacts archive.
168-
169-
Args:
170-
path: Path of the artifact
171-
streamed: If True the data will be processed by chunks of
172-
`chunk_size` and each chunk is passed to `action` for
173-
treatment
174-
iterator: If True directly return the underlying response
175-
iterator
176-
action: Callable responsible of dealing with chunk of
177-
data
178-
chunk_size: Size of each chunk
179-
**kwargs: Extra options to send to the server (e.g. sudo)
180-
181-
Raises:
182-
GitlabAuthenticationError: If authentication is not correct
183-
GitlabGetError: If the artifacts could not be retrieved
184-
185-
Returns:
186-
The artifacts if `streamed` is False, None otherwise.
187-
"""
188-
path = f"{self.manager.path}/{self.encoded_id}/artifacts/{path}"
189-
result = self.manager.gitlab.http_get(
190-
path, streamed=streamed, raw=True, **kwargs
191-
)
192-
if TYPE_CHECKING:
193-
assert isinstance(result, requests.Response)
194-
return utils.response_content(
195-
result, streamed, action, chunk_size, iterator=iterator
196-
)
125+
return self.artifacts.raw(*args, **kwargs)
197126

198127
@cli.register_custom_action("ProjectJob")
199128
@exc.on_http_error(exc.GitlabGetError)

0 commit comments

Comments
 (0)