Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ This library enables you to manage Artifactory resources such as users, groups,
+ [Create build](#create-build)
+ [Promote a build](#promote-a-build)
+ [Delete one or more builds](#delete-one-or-more-builds)
+ [Delete all build numbers of a build](#delete-all-build-numbers-of-a-build)
+ [Rename a build](#rename-a-build)
+ [Get differences between two builds](#get-differences-between-two-builds)
* [Contributing](#contributing)
Expand Down Expand Up @@ -553,6 +554,12 @@ build_delete_request = BuildDeleteRequest(buildName="<build_name>", buildNumbers
art.builds.delete(build_delete_request)
```

#### Delete all build numbers of a build
```python
build_delete_request = BuildDeleteRequest(buildName="<build_name>", deleteAll=True)
art.builds.delete(build_delete_request)
```

#### Rename a build
```python
art.builds.build_rename("<build_name>", "<new_build_name>")
Expand Down
25 changes: 23 additions & 2 deletions pyartifactory/models/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

from typing import Dict, List, Optional

from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, model_serializer, model_validator
from pydantic_core import PydanticCustomError
from typing_extensions import Self


class BuildProperties(BaseModel):
Expand Down Expand Up @@ -127,10 +129,29 @@ class BuildPromotionRequest(BaseModel):
class BuildDeleteRequest(BaseModel):
project: str = ""
buildName: str
buildNumbers: List[str]
buildNumbers: List[str] = []
deleteArtifacts: bool = False
deleteAll: bool = False

@model_validator(mode="after")
def check_numbers_or_delete_all(self) -> Self:
if self.buildNumbers or self.deleteAll:
return self
raise PydanticCustomError("request_schema_error", "Either buildNumbers or deleteAll must be specified", None)

@model_serializer()
def serialize_model(self):
d = {
"project": self.project,
"buildName": self.buildName,
"buildNumbers": self.buildNumbers,
"deleteArtifacts": self.deleteArtifacts,
"deleteAll": self.deleteAll,
}
if self.deleteAll:
d.pop("buildNumbers")
Comment thread
anancarv marked this conversation as resolved.
return d


class BuildDiffResponseDetail(BaseModel):
updated: List[str] = []
Expand Down
18 changes: 12 additions & 6 deletions pyartifactory/objects/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,13 +120,19 @@ def delete(self, delete_build: BuildDeleteRequest) -> None:
:return: None
"""
try:
for _build_number in delete_build.buildNumbers:
self._get(
f"api/{self._uri}/{delete_build.buildName}/{_build_number}",
)
# all build numbers exist
if delete_build.buildNumbers:
for _build_number in delete_build.buildNumbers:
self._get(
f"api/{self._uri}/{delete_build.buildName}/{_build_number}",
)
# all build numbers exist

self._post(f"api/{self._uri}/delete", json=delete_build.model_dump())
logger.debug("Builds %s deleted from %s", ",".join(delete_build.buildNumbers), delete_build.buildName)

if delete_build.buildNumbers:
logger.debug("Builds %s deleted from %s", ",".join(delete_build.buildNumbers), delete_build.buildName)
else:
logger.debug("Deleted all builds of %s", delete_build.buildName)
except requests.exceptions.HTTPError as error:
self._raise_exception(error)

Expand Down
35 changes: 26 additions & 9 deletions tests/test_build.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
BUILD_DIFF = BuildDiffResponse()
BUILD_PROMOTION_REQUEST = BuildPromotionRequest(sourceRepo="repo-abc", targetRepo="repo-def")
BUILD_PROMOTION_RESPONSE = BuildPromotionResult()
BUILD_DELETE_REQUEST = BuildDeleteRequest(buildName="build", buildNumbers=["abc", "123"])
BUILD_DELETE_BUILDNUMBERS_REQUEST = BuildDeleteRequest(buildName="build", buildNumbers=["abc", "123"])
BUILD_DELETE_ERROR = BuildError(errors=[{"status": 404, "message": "Not found"}])
BUILD_CREATE_REQUEST = BuildCreateRequest(name="a-build", number="build-xx", started="2014-09-30T12:00:19.893+0300")

Expand Down Expand Up @@ -170,41 +170,58 @@ def test_list_build(mocker):


@responses.activate
def test_delete_build_success(mocker):
for _build_number in BUILD_DELETE_REQUEST.buildNumbers:
def test_delete_buildnumbers_success(mocker):
for _build_number in BUILD_DELETE_BUILDNUMBERS_REQUEST.buildNumbers:
responses.add(
responses.GET,
f"{URL}/api/build/{BUILD_DELETE_REQUEST.buildName}/{_build_number}",
f"{URL}/api/build/{BUILD_DELETE_BUILDNUMBERS_REQUEST.buildName}/{_build_number}",
json=BUILD_INFO.model_dump(),
status=200,
)

responses.add(responses.POST, f"{URL}/api/build/delete", json=BUILD_DELETE_REQUEST.model_dump(), status=200)
responses.add(
responses.POST,
f"{URL}/api/build/delete",
json=BUILD_DELETE_BUILDNUMBERS_REQUEST.model_dump(),
status=200,
)

artifactory_build = ArtifactoryBuild(AuthModel(url=URL, auth=AUTH))
mocker.spy(artifactory_build, "delete")
artifactory_build.delete(BUILD_DELETE_BUILDNUMBERS_REQUEST)


@responses.activate
def test_delete_all_success(mocker):
delete_all_request = BuildDeleteRequest(buildName="build", deleteAll=True)
assert "buildNumbers" not in delete_all_request.model_dump()

responses.add(responses.POST, f"{URL}/api/build/delete", json=delete_all_request.model_dump(), status=200)

artifactory_build = ArtifactoryBuild(AuthModel(url=URL, auth=AUTH))
mocker.spy(artifactory_build, "delete")
artifactory_build.delete(BUILD_DELETE_REQUEST)
artifactory_build.delete(delete_all_request)


@responses.activate
def test_delete_build_error_not_exist(mocker):
responses.add(
responses.GET,
f"{URL}/api/build/{BUILD_DELETE_REQUEST.buildName}/{BUILD_DELETE_REQUEST.buildNumbers[0]}",
f"{URL}/api/build/{BUILD_DELETE_BUILDNUMBERS_REQUEST.buildName}/{BUILD_DELETE_BUILDNUMBERS_REQUEST.buildNumbers[0]}",
json=BUILD_INFO.model_dump(),
status=200,
)
responses.add(
responses.GET,
f"{URL}/api/build/{BUILD_DELETE_REQUEST.buildName}/{BUILD_DELETE_REQUEST.buildNumbers[-1]}",
f"{URL}/api/build/{BUILD_DELETE_BUILDNUMBERS_REQUEST.buildName}/{BUILD_DELETE_BUILDNUMBERS_REQUEST.buildNumbers[-1]}",
body=NOT_FOUND_EXCEPTION_BODY,
status=200,
)

artifactory_build = ArtifactoryBuild(AuthModel(url=URL, auth=AUTH))
mocker.spy(artifactory_build, "delete")
with pytest.raises(ArtifactoryError):
artifactory_build.delete(BUILD_DELETE_REQUEST)
artifactory_build.delete(BUILD_DELETE_BUILDNUMBERS_REQUEST)


@responses.activate
Expand Down
Loading