231 lines
7.1 KiB
Python
231 lines
7.1 KiB
Python
"""
|
|
GitLab API:
|
|
https://docs.gitlab.com/ee/api/packages.html
|
|
https://docs.gitlab.com/ee/user/packages/generic_packages/
|
|
"""
|
|
|
|
from pathlib import Path
|
|
from typing import (
|
|
Any,
|
|
BinaryIO,
|
|
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 DeleteMixin, GetMixin, ListMixin, ObjectDeleteMixin
|
|
|
|
__all__ = [
|
|
"GenericPackage",
|
|
"GenericPackageManager",
|
|
"GroupPackage",
|
|
"GroupPackageManager",
|
|
"ProjectPackage",
|
|
"ProjectPackageManager",
|
|
"ProjectPackageFile",
|
|
"ProjectPackageFileManager",
|
|
"ProjectPackagePipeline",
|
|
"ProjectPackagePipelineManager",
|
|
]
|
|
|
|
|
|
class GenericPackage(RESTObject):
|
|
_id_attr = "package_name"
|
|
|
|
|
|
class GenericPackageManager(RESTManager):
|
|
_path = "/projects/{project_id}/packages/generic"
|
|
_obj_cls = GenericPackage
|
|
_from_parent_attrs = {"project_id": "id"}
|
|
|
|
@cli.register_custom_action(
|
|
cls_names="GenericPackageManager",
|
|
required=("package_name", "package_version", "file_name", "path"),
|
|
)
|
|
@exc.on_http_error(exc.GitlabUploadError)
|
|
def upload(
|
|
self,
|
|
package_name: str,
|
|
package_version: str,
|
|
file_name: str,
|
|
path: Optional[Union[str, Path]] = None,
|
|
select: Optional[str] = None,
|
|
data: Optional[Union[bytes, BinaryIO]] = None,
|
|
**kwargs: Any,
|
|
) -> GenericPackage:
|
|
"""Upload a file as a generic package.
|
|
|
|
Args:
|
|
package_name: The package name. Must follow generic package
|
|
name regex rules
|
|
package_version: The package version. Must follow semantic
|
|
version regex rules
|
|
file_name: The name of the file as uploaded in the registry
|
|
path: The path to a local file to upload
|
|
select: GitLab API accepts a value of 'package_file'
|
|
|
|
Raises:
|
|
GitlabConnectionError: If the server cannot be reached
|
|
GitlabUploadError: If the file upload fails
|
|
GitlabUploadError: If ``path`` cannot be read
|
|
GitlabUploadError: If both ``path`` and ``data`` are passed
|
|
|
|
Returns:
|
|
An object storing the metadata of the uploaded package.
|
|
|
|
https://docs.gitlab.com/ee/user/packages/generic_packages/
|
|
"""
|
|
|
|
if path is None and data is None:
|
|
raise exc.GitlabUploadError("No file contents or path specified")
|
|
|
|
if path is not None and data is not None:
|
|
raise exc.GitlabUploadError("File contents and file path specified")
|
|
|
|
file_data: Optional[Union[bytes, BinaryIO]] = data
|
|
|
|
if not file_data:
|
|
if TYPE_CHECKING:
|
|
assert path is not None
|
|
|
|
try:
|
|
with open(path, "rb") as f:
|
|
file_data = f.read()
|
|
except OSError as e:
|
|
raise exc.GitlabUploadError(
|
|
f"Failed to read package file {path}"
|
|
) from e
|
|
|
|
url = f"{self._computed_path}/{package_name}/{package_version}/{file_name}"
|
|
query_data = {} if select is None else {"select": select}
|
|
server_data = self.gitlab.http_put(
|
|
url, query_data=query_data, post_data=file_data, raw=True, **kwargs
|
|
)
|
|
if TYPE_CHECKING:
|
|
assert isinstance(server_data, dict)
|
|
|
|
attrs = {
|
|
"package_name": package_name,
|
|
"package_version": package_version,
|
|
"file_name": file_name,
|
|
"path": path,
|
|
}
|
|
attrs.update(server_data)
|
|
return self._obj_cls(self, attrs=attrs)
|
|
|
|
@cli.register_custom_action(
|
|
cls_names="GenericPackageManager",
|
|
required=("package_name", "package_version", "file_name"),
|
|
)
|
|
@exc.on_http_error(exc.GitlabGetError)
|
|
def download(
|
|
self,
|
|
package_name: str,
|
|
package_version: str,
|
|
file_name: str,
|
|
streamed: bool = False,
|
|
action: Optional[Callable[[bytes], None]] = None,
|
|
chunk_size: int = 1024,
|
|
*,
|
|
iterator: bool = False,
|
|
**kwargs: Any,
|
|
) -> Optional[Union[bytes, Iterator[Any]]]:
|
|
"""Download a generic package.
|
|
|
|
Args:
|
|
package_name: The package name.
|
|
package_version: The package version.
|
|
file_name: The name of the file in the registry
|
|
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 server failed to perform the request
|
|
|
|
Returns:
|
|
The package content if streamed is False, None otherwise
|
|
"""
|
|
path = f"{self._computed_path}/{package_name}/{package_version}/{file_name}"
|
|
result = self.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 GroupPackage(RESTObject):
|
|
pass
|
|
|
|
|
|
class GroupPackageManager(ListMixin, RESTManager):
|
|
_path = "/groups/{group_id}/packages"
|
|
_obj_cls = GroupPackage
|
|
_from_parent_attrs = {"group_id": "id"}
|
|
_list_filters = (
|
|
"exclude_subgroups",
|
|
"order_by",
|
|
"sort",
|
|
"package_type",
|
|
"package_name",
|
|
)
|
|
|
|
|
|
class ProjectPackage(ObjectDeleteMixin, RESTObject):
|
|
package_files: "ProjectPackageFileManager"
|
|
pipelines: "ProjectPackagePipelineManager"
|
|
|
|
|
|
class ProjectPackageManager(ListMixin, GetMixin, DeleteMixin, RESTManager):
|
|
_path = "/projects/{project_id}/packages"
|
|
_obj_cls = ProjectPackage
|
|
_from_parent_attrs = {"project_id": "id"}
|
|
_list_filters = (
|
|
"order_by",
|
|
"sort",
|
|
"package_type",
|
|
"package_name",
|
|
)
|
|
|
|
def get(
|
|
self, id: Union[str, int], lazy: bool = False, **kwargs: Any
|
|
) -> ProjectPackage:
|
|
return cast(ProjectPackage, super().get(id=id, lazy=lazy, **kwargs))
|
|
|
|
|
|
class ProjectPackageFile(ObjectDeleteMixin, RESTObject):
|
|
pass
|
|
|
|
|
|
class ProjectPackageFileManager(DeleteMixin, ListMixin, RESTManager):
|
|
_path = "/projects/{project_id}/packages/{package_id}/package_files"
|
|
_obj_cls = ProjectPackageFile
|
|
_from_parent_attrs = {"project_id": "project_id", "package_id": "id"}
|
|
|
|
|
|
class ProjectPackagePipeline(RESTObject):
|
|
pass
|
|
|
|
|
|
class ProjectPackagePipelineManager(ListMixin, RESTManager):
|
|
_path = "/projects/{project_id}/packages/{package_id}/pipelines"
|
|
_obj_cls = ProjectPackagePipeline
|
|
_from_parent_attrs = {"project_id": "project_id", "package_id": "id"}
|