snapshot merge: use proper REST api

- this breaks the existing api, which is only available in CI builds
- improve swagger doc
This commit is contained in:
André Roth
2024-10-03 15:18:13 +02:00
parent 06b2b920da
commit 38ea720fc5
3 changed files with 65 additions and 38 deletions

View File

@@ -200,7 +200,7 @@ func Router(c *ctx.AptlyContext) http.Handler {
api.GET("/snapshots/:name/packages", apiSnapshotsSearchPackages)
api.DELETE("/snapshots/:name", apiSnapshotsDrop)
api.GET("/snapshots/:name/diff/:withSnapshot", apiSnapshotsDiff)
api.POST("/snapshots/merge", apiSnapshotsMerge)
api.POST("/snapshots/:name/merge", apiSnapshotsMerge)
api.POST("/snapshots/:name/pull", apiSnapshotsPull)
}

View File

@@ -406,17 +406,37 @@ func apiSnapshotsSearchPackages(c *gin.Context) {
showPackages(c, snapshot.RefList(), collectionFactory)
}
// POST /api/snapshots/merge
type snapshotsMergeParams struct {
// List of snapshot names to be merged
Sources []string `binding:"required"`
}
// @Summary Snapshot Merge
// @Description **Merge several source snapshots into a new snapshot**
// @Description
// @Description Merge happens from left to right. By default, packages with the same name-architecture pair are replaced during merge (package from latest snapshot on the list wins).
// @Description
// @Description If only one snapshot is specified, merge copies source into destination.
// @Tags Snapshots
// @Param latest query int false "merge only the latest version of each package"
// @Param no-remove query int false "all versions of packages are preserved during merge"
// @Accept json
// @Param name path string true "Name of the snapshot to be created"
// @Param request body snapshotsMergeParams true "json parameters"
// @Produce json
// @Success 200
// @Failure 400 {object} Error "Bad Request"
// @Failure 404 {object} Error "Not Found"
// @Failure 500 {object} Error "Internal Error"
// @Router /api/snapshots/{name}/merge [post]
func apiSnapshotsMerge(c *gin.Context) {
var (
err error
snapshot *deb.Snapshot
body snapshotsMergeParams
)
var body struct {
Destination string `binding:"required"`
Sources []string `binding:"required"`
}
name := c.Params.ByName("name")
if c.Bind(&body) != nil {
return
@@ -456,7 +476,7 @@ func apiSnapshotsMerge(c *gin.Context) {
resources[i] = string(sources[i].ResourceKey())
}
maybeRunTaskInBackground(c, "Merge snapshot "+body.Destination, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
maybeRunTaskInBackground(c, "Merge snapshot "+name, resources, func(_ aptly.Progress, _ *task.Detail) (*task.ProcessReturnValue, error) {
result := sources[0].RefList()
for i := 1; i < len(sources); i++ {
result = result.Merge(sources[i].RefList(), overrideMatching, false)
@@ -471,7 +491,7 @@ func apiSnapshotsMerge(c *gin.Context) {
sourceDescription[i] = fmt.Sprintf("'%s'", s.Name)
}
snapshot = deb.NewSnapshotFromRefList(body.Destination, sources, result,
snapshot = deb.NewSnapshotFromRefList(name, sources, result,
fmt.Sprintf("Merged from sources: %s", strings.Join(sourceDescription, ", ")))
err = collectionFactory.SnapshotCollection().Add(snapshot)
@@ -483,27 +503,36 @@ func apiSnapshotsMerge(c *gin.Context) {
})
}
type snapshotsPullBody struct {
// Source name where packages and dependencies will be searched
type snapshotsPullParams struct {
// Source name to be searched for packages and dependencies
Source string `binding:"required" json:"Source" example:"source-snapshot"`
// Name of the snapshot that will be created
// Name of the snapshot to be created
Destination string `binding:"required" json:"Destination" example:"idestination-snapshot"`
// List of package queries, in the simplest form, name of package to be pulled from
// List of package queries (i.e. name of package to be pulled from `Source`)
Queries []string `binding:"required" json:"Queries" example:"xserver-xorg"`
// List of architectures (optional)
Architectures []string ` json:"Architectures" example:"amd64, armhf"`
}
// @Summary Snapshot Pull
// @Description Pulls new packages (along with its dependencies) to name snapshot from source snapshot. Also pull command can upgrade package versions if name snapshot already contains packages being pulled. New snapshot destination is created as result of this process.
// @Description **Pulls new packages and dependencies from a source snapshot into a new snapshot**
// @Description
// @Description May also upgrade package versions if name snapshot already contains packages being pulled. New snapshot `Destination` is created as result of this process.
// @Description If architectures are limited (with config architectures or parameter `Architectures`, only mentioned architectures are processed, otherwise aptly will process all architectures in the snapshot.
// @Description If following dependencies by source is enabled (using dependencyFollowSource config), pulling binary packages would also pull corresponding source packages as well.
// @Description By default aptly would remove packages matching name and architecture while importing: e.g. when importing software_1.3_amd64, package software_1.2.9_amd64 would be removed.
// @Description
// @Description With flag `no-remove` both package versions would stay in the snapshot.
// @Description
// @Description Aptly pulls first package matching each of package queries, but with flag -all-matches all matching packages would be pulled.
// @Tags Snapshots
// @Param all-matches query int false "all-matches: 1 to enable"
// @Param dry-run query int false "dry-run: 1 to enable"
// @Param no-deps query int false "no-deps: 1 to enable"
// @Param no-remove query int false "no-remove: 1 to enable"
// @Param all-matches query int false "pull all the packages that satisfy the dependency version requirements (default is to pull first matching package): 1 to enable"
// @Param dry-run query int false "dont create destination snapshot, just show what would be pulled: 1 to enable"
// @Param no-deps query int false "dont process dependencies, just pull listed packages: 1 to enable"
// @Param no-remove query int false "dont remove other package versions when pulling package: 1 to enable"
// @Accept json
// @Param name path string true "Snapshot where packages and dependencies will be pulled to"
// @Param request body snapshotsPullBody true "See api.snapshotsPullBody"
// @Param name path string true "Name of the snapshot to be created"
// @Param request body snapshotsPullParams true "json parameters"
// @Produce json
// @Success 200
// @Failure 400 {object} Error "Bad Request"
@@ -514,7 +543,7 @@ func apiSnapshotsPull(c *gin.Context) {
var (
err error
destinationSnapshot *deb.Snapshot
body snapshotsPullBody
body snapshotsPullParams
)
name := c.Params.ByName("name")

View File

@@ -308,7 +308,7 @@ class SnapshotsAPITestDiff(APITest):
class SnapshotsAPITestMerge(APITest):
"""
POST /api/snapshots, POST /api/snapshots/merge, GET /api/snapshots/:name, DELETE /api/snapshots/:name
POST /api/snapshots, POST /api/snapshots/:name/merge, GET /api/snapshots/:name, DELETE /api/snapshots/:name
"""
def check(self):
@@ -325,9 +325,8 @@ class SnapshotsAPITestMerge(APITest):
# create merge snapshot
merged_name = self.random_name()
task = self.post_task(
"/api/snapshots/merge",
f"/api/snapshots/{merged_name}/merge",
json={
"Destination": merged_name,
"Sources": [source["Name"] for source in sources],
},
)
@@ -352,7 +351,7 @@ class SnapshotsAPITestMerge(APITest):
# create merge snapshot without sources
merged_name = self.random_name()
resp = self.post(
"/api/snapshots/merge", json={"Destination": merged_name, "Sources": []}
f"/api/snapshots/{merged_name}/merge", json={"Sources": []}
)
self.check_equal(resp.status_code, 400)
self.check_equal(
@@ -364,8 +363,8 @@ class SnapshotsAPITestMerge(APITest):
merged_name = self.random_name()
non_existing_source = self.random_name()
resp = self.post(
"/api/snapshots/merge",
json={"Destination": merged_name, "Sources": [non_existing_source]},
f"/api/snapshots/{merged_name}/merge",
json={"Sources": [non_existing_source]},
)
self.check_equal(
resp.json()["error"], f"snapshot with name {non_existing_source} not found"
@@ -377,8 +376,8 @@ class SnapshotsAPITestMerge(APITest):
# create merge snapshot with used name
merged_name = sources[0]["Name"]
resp = self.post(
"/api/snapshots/merge",
json={"Destination": merged_name, "Sources": [source["Name"] for source in sources]},
f"/api/snapshots/{merged_name}/merge",
json={"Sources": [source["Name"] for source in sources]},
)
self.check_equal(
resp.json()["error"],
@@ -389,9 +388,8 @@ class SnapshotsAPITestMerge(APITest):
# create merge snapshot with "latest" and "no-remove" flags (should fail)
merged_name = self.random_name()
resp = self.post(
"/api/snapshots/merge",
f"/api/snapshots/{merged_name}/merge",
json={
"Destination": merged_name,
"Sources": [source["Name"] for source in sources],
},
params={"latest": "1", "no-remove": "1"},
@@ -404,7 +402,7 @@ class SnapshotsAPITestMerge(APITest):
class SnapshotsAPITestPull(APITest):
"""
POST /api/snapshots/pull, POST /api/snapshots, GET /api/snapshots/:name/packages?name=:package_name
POST /api/snapshots/:name/pull, POST /api/snapshots, GET /api/snapshots/:name/packages?name=:package_name
"""
def check(self):
@@ -448,7 +446,7 @@ class SnapshotsAPITestPull(APITest):
self.check_equal(resp.status_code, 200)
# dry run, all-matches
resp = self.post("/api/snapshots/{snapshot_empty_repo}/pull?dry-run=1&all-matches=1", json={
resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull?dry-run=1&all-matches=1", json={
'Source': snapshot_repo_with_libboost,
'Destination': snapshot_pull_libboost,
'Queries': [
@@ -462,14 +460,14 @@ class SnapshotsAPITestPull(APITest):
self.check_equal(resp.status_code, 200)
# missing argument
resp = self.post("/api/snapshots/{snapshot_empty_repo}/pull", json={
resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull", json={
'Source': snapshot_repo_with_libboost,
'Destination': snapshot_pull_libboost,
})
self.check_equal(resp.status_code, 400)
# dry run, emtpy architectures
resp = self.post("/api/snapshots/{snapshot_empty_repo}/pull?dry-run=1", json={
resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull?dry-run=1", json={
'Source': snapshot_repo_with_libboost,
'Destination': snapshot_pull_libboost,
'Queries': [
@@ -490,7 +488,7 @@ class SnapshotsAPITestPull(APITest):
self.check_equal(resp.status_code, 404)
# dry run, non-existing source
resp = self.post("/api/snapshots/{snapshot_empty_repo}/pull?dry-run=1", json={
resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull?dry-run=1", json={
'Source': "asd123",
'Destination': snapshot_pull_libboost,
'Queries': [
@@ -500,7 +498,7 @@ class SnapshotsAPITestPull(APITest):
self.check_equal(resp.status_code, 404)
# snapshot pull
resp = self.post("/api/snapshots/{snapshot_empty_repo}/pull", json={
resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull", json={
'Source': snapshot_repo_with_libboost,
'Destination': snapshot_pull_libboost,
'Queries': [
@@ -525,7 +523,7 @@ class SnapshotsAPITestPull(APITest):
# pull from non-existing source
non_existing_source = self.random_name()
destination = self.random_name()
resp = self.post("/api/snapshots/{snapshot_empty_repo}/pull", json={
resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull", json={
'Source': non_existing_source,
'Destination': destination,
'Queries': [
@@ -541,7 +539,7 @@ class SnapshotsAPITestPull(APITest):
# pull to non-existing snapshot
non_existing_snapshot = self.random_name()
destination = self.random_name()
resp = self.post("/api/snapshots/{snapshot_empty_repo}/pull", json={
resp = self.post(f"/api/snapshots/{snapshot_empty_repo}/pull", json={
'Source': non_existing_snapshot,
'Destination': destination,
'Queries': [