This commit is contained in:
2024-12-09 18:22:38 +09:00
parent ab0cbebefc
commit c4c4547706
959 changed files with 174888 additions and 6 deletions

View File

@ -0,0 +1,230 @@
"""
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"}