Skip to content

Commit cf2e6f9

Browse files
author
Eero Aaltonen
committed
Add support for deleting all builds of build name
deleteAll parameter, added in Artifactory 2.3.0, allows to delete all build numbers of a specified build name.
1 parent 75115e0 commit cf2e6f9

File tree

4 files changed

+68
-17
lines changed

4 files changed

+68
-17
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ This library enables you to manage Artifactory resources such as users, groups,
4949
+ [Create build](#create-build)
5050
+ [Promote a build](#promote-a-build)
5151
+ [Delete one or more builds](#delete-one-or-more-builds)
52+
+ [Delete all build numbers of a build](#delete-all-build-numbers-of-a-build)
5253
+ [Rename a build](#rename-a-build)
5354
+ [Get differences between two builds](#get-differences-between-two-builds)
5455
* [Contributing](#contributing)
@@ -553,6 +554,12 @@ build_delete_request = BuildDeleteRequest(buildName="<build_name>", buildNumbers
553554
art.builds.delete(build_delete_request)
554555
```
555556

557+
#### Delete all build numbers of a build
558+
```python
559+
build_delete_request = BuildDeleteRequest(buildName="<build_name>", deleteAll=True)
560+
art.builds.delete(build_delete_request)
561+
```
562+
556563
#### Rename a build
557564
```python
558565
art.builds.build_rename("<build_name>", "<new_build_name>")

pyartifactory/models/build.py

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55

66
from typing import Dict, List, Optional
77

8-
from pydantic import BaseModel, Field
8+
from pydantic import BaseModel, Field, model_serializer, model_validator
9+
from pydantic_core import PydanticCustomError
10+
from typing_extensions import Self
911

1012

1113
class BuildProperties(BaseModel):
@@ -127,10 +129,29 @@ class BuildPromotionRequest(BaseModel):
127129
class BuildDeleteRequest(BaseModel):
128130
project: str = ""
129131
buildName: str
130-
buildNumbers: List[str]
132+
buildNumbers: List[str] = []
131133
deleteArtifacts: bool = False
132134
deleteAll: bool = False
133135

136+
@model_validator(mode="after")
137+
def check_numbers_or_delete_all(self) -> Self:
138+
if self.buildNumbers or self.deleteAll:
139+
return self
140+
raise PydanticCustomError("request_schema_error", "Either buildNumbers or deleteAll must be specified", None)
141+
142+
@model_serializer()
143+
def serialize_model(self):
144+
d = {
145+
"project": self.project,
146+
"buildName": self.buildName,
147+
"buildNumbers": self.buildNumbers,
148+
"deleteArtifacts": self.deleteArtifacts,
149+
"deleteAll": self.deleteAll,
150+
}
151+
if self.deleteAll:
152+
d.pop("buildNumbers")
153+
return d
154+
134155

135156
class BuildDiffResponseDetail(BaseModel):
136157
updated: List[str] = []

pyartifactory/objects/build.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,19 @@ def delete(self, delete_build: BuildDeleteRequest) -> None:
120120
:return: None
121121
"""
122122
try:
123-
for _build_number in delete_build.buildNumbers:
124-
self._get(
125-
f"api/{self._uri}/{delete_build.buildName}/{_build_number}",
126-
)
127-
# all build numbers exist
123+
if delete_build.buildNumbers:
124+
for _build_number in delete_build.buildNumbers:
125+
self._get(
126+
f"api/{self._uri}/{delete_build.buildName}/{_build_number}",
127+
)
128+
# all build numbers exist
129+
128130
self._post(f"api/{self._uri}/delete", json=delete_build.model_dump())
129-
logger.debug("Builds %s deleted from %s", ",".join(delete_build.buildNumbers), delete_build.buildName)
131+
132+
if delete_build.buildNumbers:
133+
logger.debug("Builds %s deleted from %s", ",".join(delete_build.buildNumbers), delete_build.buildName)
134+
else:
135+
logger.debug("Deleted all builds of %s", delete_build.buildName)
130136
except requests.exceptions.HTTPError as error:
131137
self._raise_exception(error)
132138

tests/test_build.py

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
BUILD_DIFF = BuildDiffResponse()
3131
BUILD_PROMOTION_REQUEST = BuildPromotionRequest(sourceRepo="repo-abc", targetRepo="repo-def")
3232
BUILD_PROMOTION_RESPONSE = BuildPromotionResult()
33-
BUILD_DELETE_REQUEST = BuildDeleteRequest(buildName="build", buildNumbers=["abc", "123"])
33+
BUILD_DELETE_BUILDNUMBERS_REQUEST = BuildDeleteRequest(buildName="build", buildNumbers=["abc", "123"])
3434
BUILD_DELETE_ERROR = BuildError(errors=[{"status": 404, "message": "Not found"}])
3535
BUILD_CREATE_REQUEST = BuildCreateRequest(name="a-build", number="build-xx", started="2014-09-30T12:00:19.893+0300")
3636

@@ -170,41 +170,58 @@ def test_list_build(mocker):
170170

171171

172172
@responses.activate
173-
def test_delete_build_success(mocker):
174-
for _build_number in BUILD_DELETE_REQUEST.buildNumbers:
173+
def test_delete_buildnumbers_success(mocker):
174+
for _build_number in BUILD_DELETE_BUILDNUMBERS_REQUEST.buildNumbers:
175175
responses.add(
176176
responses.GET,
177-
f"{URL}/api/build/{BUILD_DELETE_REQUEST.buildName}/{_build_number}",
177+
f"{URL}/api/build/{BUILD_DELETE_BUILDNUMBERS_REQUEST.buildName}/{_build_number}",
178178
json=BUILD_INFO.model_dump(),
179179
status=200,
180180
)
181181

182-
responses.add(responses.POST, f"{URL}/api/build/delete", json=BUILD_DELETE_REQUEST.model_dump(), status=200)
182+
responses.add(
183+
responses.POST,
184+
f"{URL}/api/build/delete",
185+
json=BUILD_DELETE_BUILDNUMBERS_REQUEST.model_dump(),
186+
status=200,
187+
)
188+
189+
artifactory_build = ArtifactoryBuild(AuthModel(url=URL, auth=AUTH))
190+
mocker.spy(artifactory_build, "delete")
191+
artifactory_build.delete(BUILD_DELETE_BUILDNUMBERS_REQUEST)
192+
193+
194+
@responses.activate
195+
def test_delete_all_success(mocker):
196+
delete_all_request = BuildDeleteRequest(buildName="build", deleteAll=True)
197+
assert "buildNumbers" not in delete_all_request.model_dump()
198+
199+
responses.add(responses.POST, f"{URL}/api/build/delete", json=delete_all_request.model_dump(), status=200)
183200

184201
artifactory_build = ArtifactoryBuild(AuthModel(url=URL, auth=AUTH))
185202
mocker.spy(artifactory_build, "delete")
186-
artifactory_build.delete(BUILD_DELETE_REQUEST)
203+
artifactory_build.delete(delete_all_request)
187204

188205

189206
@responses.activate
190207
def test_delete_build_error_not_exist(mocker):
191208
responses.add(
192209
responses.GET,
193-
f"{URL}/api/build/{BUILD_DELETE_REQUEST.buildName}/{BUILD_DELETE_REQUEST.buildNumbers[0]}",
210+
f"{URL}/api/build/{BUILD_DELETE_BUILDNUMBERS_REQUEST.buildName}/{BUILD_DELETE_BUILDNUMBERS_REQUEST.buildNumbers[0]}",
194211
json=BUILD_INFO.model_dump(),
195212
status=200,
196213
)
197214
responses.add(
198215
responses.GET,
199-
f"{URL}/api/build/{BUILD_DELETE_REQUEST.buildName}/{BUILD_DELETE_REQUEST.buildNumbers[-1]}",
216+
f"{URL}/api/build/{BUILD_DELETE_BUILDNUMBERS_REQUEST.buildName}/{BUILD_DELETE_BUILDNUMBERS_REQUEST.buildNumbers[-1]}",
200217
body=NOT_FOUND_EXCEPTION_BODY,
201218
status=200,
202219
)
203220

204221
artifactory_build = ArtifactoryBuild(AuthModel(url=URL, auth=AUTH))
205222
mocker.spy(artifactory_build, "delete")
206223
with pytest.raises(ArtifactoryError):
207-
artifactory_build.delete(BUILD_DELETE_REQUEST)
224+
artifactory_build.delete(BUILD_DELETE_BUILDNUMBERS_REQUEST)
208225

209226

210227
@responses.activate

0 commit comments

Comments
 (0)